Merge "Move UiMode broadcasts to foreground"
diff --git a/Android.mk b/Android.mk
index 4fea0d4..0b5e0cc 100644
--- a/Android.mk
+++ b/Android.mk
@@ -73,6 +73,7 @@
core/java/android/app/IAlarmListener.aidl \
core/java/android/app/IAlarmManager.aidl \
core/java/android/app/IAppTask.aidl \
+ core/java/android/app/IApplicationThread.aidl \
core/java/android/app/ITaskStackListener.aidl \
core/java/android/app/IBackupAgent.aidl \
core/java/android/app/IEphemeralResolver.aidl \
@@ -574,6 +575,7 @@
frameworks/base/core/java/android/accounts/AuthenticatorDescription.aidl \
frameworks/base/core/java/android/accounts/Account.aidl \
frameworks/base/core/java/android/app/admin/SystemUpdatePolicy.aidl \
+ frameworks/base/core/java/android/app/admin/PasswordMetrics.aidl \
frameworks/base/core/java/android/print/PrintDocumentInfo.aidl \
frameworks/base/core/java/android/print/PageRange.aidl \
frameworks/base/core/java/android/print/PrintAttributes.aidl \
@@ -597,6 +599,7 @@
frameworks/base/core/java/android/os/WorkSource.aidl \
frameworks/base/core/java/android/os/DropBoxManager.aidl \
frameworks/base/core/java/android/os/Bundle.aidl \
+ frameworks/base/core/java/android/os/Debug.aidl \
frameworks/base/core/java/android/accessibilityservice/AccessibilityServiceInfo.aidl \
frameworks/base/core/java/android/net/Network.aidl \
frameworks/base/core/java/android/net/RouteInfo.aidl \
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index f5bd945..c28db57 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,7 +1,16 @@
[Hook Scripts]
checkstyle_hook = ../../development/tools/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
-fw core/java/android/animation/
+ core/java/android/hardware/usb/
+ core/java/android/print/
+ core/java/android/printservice/
core/java/android/text/
- core/java/android/view/
core/java/android/transition/
+ core/java/android/view/
core/java/android/widget/
+ core/tests/coretests/src/android/print/
+ packages/PrintRecommendationService/
+ packages/PrintSpooler/
+ services/print/
+ services/usb/
+
diff --git a/api/current.txt b/api/current.txt
index 58d3341..564653a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4489,6 +4489,7 @@
method public void onTrimMemory(int);
method public void onViewCreated(android.view.View, android.os.Bundle);
method public void onViewStateRestored(android.os.Bundle);
+ method public void postponeEnterTransition();
method public void registerForContextMenu(android.view.View);
method public final void requestPermissions(java.lang.String[], int);
method public void setAllowEnterTransitionOverlap(boolean);
@@ -4514,6 +4515,7 @@
method public void startActivityForResult(android.content.Intent, int);
method public void startActivityForResult(android.content.Intent, int, android.os.Bundle);
method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
+ method public void startPostponedEnterTransition();
method public void unregisterForContextMenu(android.view.View);
}
@@ -4671,6 +4673,7 @@
method public abstract android.app.FragmentTransaction remove(android.app.Fragment);
method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment);
method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment, java.lang.String);
+ method public abstract android.app.FragmentTransaction setAllowOptimization(boolean);
method public abstract android.app.FragmentTransaction setBreadCrumbShortTitle(int);
method public abstract android.app.FragmentTransaction setBreadCrumbShortTitle(java.lang.CharSequence);
method public abstract android.app.FragmentTransaction setBreadCrumbTitle(int);
@@ -13899,6 +13902,7 @@
public static abstract class CameraCaptureSession.StateCallback {
ctor public CameraCaptureSession.StateCallback();
method public void onActive(android.hardware.camera2.CameraCaptureSession);
+ method public void onCaptureQueueEmpty(android.hardware.camera2.CameraCaptureSession);
method public void onClosed(android.hardware.camera2.CameraCaptureSession);
method public abstract void onConfigureFailed(android.hardware.camera2.CameraCaptureSession);
method public abstract void onConfigured(android.hardware.camera2.CameraCaptureSession);
@@ -44023,8 +44027,8 @@
field public static final int ROTATION_ANIMATION_CHANGED = 4096; // 0x1000
field public static final int ROTATION_ANIMATION_CROSSFADE = 1; // 0x1
field public static final int ROTATION_ANIMATION_JUMPCUT = 2; // 0x2
- field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
field public static final int ROTATION_ANIMATION_ROTATE = 0; // 0x0
+ field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
field public static final int SCREEN_BRIGHTNESS_CHANGED = 2048; // 0x800
field public static final int SCREEN_ORIENTATION_CHANGED = 1024; // 0x400
field public static final int SOFT_INPUT_ADJUST_NOTHING = 48; // 0x30
diff --git a/api/system-current.txt b/api/system-current.txt
index 7805ae3..fd51a6a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -103,7 +103,6 @@
field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
field public static final java.lang.String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS";
- field public static final java.lang.String GET_PACKAGE_IMPORTANCE = "android.permission.GET_PACKAGE_IMPORTANCE";
field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
field public static final java.lang.String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE";
field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS";
@@ -4634,6 +4633,7 @@
method public void onTrimMemory(int);
method public void onViewCreated(android.view.View, android.os.Bundle);
method public void onViewStateRestored(android.os.Bundle);
+ method public void postponeEnterTransition();
method public void registerForContextMenu(android.view.View);
method public final void requestPermissions(java.lang.String[], int);
method public void setAllowEnterTransitionOverlap(boolean);
@@ -4659,6 +4659,7 @@
method public void startActivityForResult(android.content.Intent, int);
method public void startActivityForResult(android.content.Intent, int, android.os.Bundle);
method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
+ method public void startPostponedEnterTransition();
method public void unregisterForContextMenu(android.view.View);
}
@@ -4816,6 +4817,7 @@
method public abstract android.app.FragmentTransaction remove(android.app.Fragment);
method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment);
method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment, java.lang.String);
+ method public abstract android.app.FragmentTransaction setAllowOptimization(boolean);
method public abstract android.app.FragmentTransaction setBreadCrumbShortTitle(int);
method public abstract android.app.FragmentTransaction setBreadCrumbShortTitle(java.lang.CharSequence);
method public abstract android.app.FragmentTransaction setBreadCrumbTitle(int);
@@ -14355,6 +14357,7 @@
public static abstract class CameraCaptureSession.StateCallback {
ctor public CameraCaptureSession.StateCallback();
method public void onActive(android.hardware.camera2.CameraCaptureSession);
+ method public void onCaptureQueueEmpty(android.hardware.camera2.CameraCaptureSession);
method public void onClosed(android.hardware.camera2.CameraCaptureSession);
method public abstract void onConfigureFailed(android.hardware.camera2.CameraCaptureSession);
method public abstract void onConfigured(android.hardware.camera2.CameraCaptureSession);
@@ -37556,8 +37559,6 @@
method public int getUser();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
- field public static final java.lang.String GROUP_KEY_OVERRIDE_KEY = "group_key_override";
- field public static final java.lang.String NEEDS_AUTOGROUPING_KEY = "autogroup_needed";
}
public final class Condition implements android.os.Parcelable {
@@ -47207,8 +47208,8 @@
field public static final int ROTATION_ANIMATION_CHANGED = 4096; // 0x1000
field public static final int ROTATION_ANIMATION_CROSSFADE = 1; // 0x1
field public static final int ROTATION_ANIMATION_JUMPCUT = 2; // 0x2
- field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
field public static final int ROTATION_ANIMATION_ROTATE = 0; // 0x0
+ field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
field public static final int SCREEN_BRIGHTNESS_CHANGED = 2048; // 0x800
field public static final int SCREEN_ORIENTATION_CHANGED = 1024; // 0x400
field public static final int SOFT_INPUT_ADJUST_NOTHING = 48; // 0x30
diff --git a/api/test-current.txt b/api/test-current.txt
index bde0e41..150fbfe 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4492,6 +4492,7 @@
method public void onTrimMemory(int);
method public void onViewCreated(android.view.View, android.os.Bundle);
method public void onViewStateRestored(android.os.Bundle);
+ method public void postponeEnterTransition();
method public void registerForContextMenu(android.view.View);
method public final void requestPermissions(java.lang.String[], int);
method public void setAllowEnterTransitionOverlap(boolean);
@@ -4517,6 +4518,7 @@
method public void startActivityForResult(android.content.Intent, int);
method public void startActivityForResult(android.content.Intent, int, android.os.Bundle);
method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
+ method public void startPostponedEnterTransition();
method public void unregisterForContextMenu(android.view.View);
}
@@ -4674,6 +4676,7 @@
method public abstract android.app.FragmentTransaction remove(android.app.Fragment);
method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment);
method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment, java.lang.String);
+ method public abstract android.app.FragmentTransaction setAllowOptimization(boolean);
method public abstract android.app.FragmentTransaction setBreadCrumbShortTitle(int);
method public abstract android.app.FragmentTransaction setBreadCrumbShortTitle(java.lang.CharSequence);
method public abstract android.app.FragmentTransaction setBreadCrumbTitle(int);
@@ -13916,6 +13919,7 @@
public static abstract class CameraCaptureSession.StateCallback {
ctor public CameraCaptureSession.StateCallback();
method public void onActive(android.hardware.camera2.CameraCaptureSession);
+ method public void onCaptureQueueEmpty(android.hardware.camera2.CameraCaptureSession);
method public void onClosed(android.hardware.camera2.CameraCaptureSession);
method public abstract void onConfigureFailed(android.hardware.camera2.CameraCaptureSession);
method public abstract void onConfigured(android.hardware.camera2.CameraCaptureSession);
@@ -44259,8 +44263,8 @@
field public static final int ROTATION_ANIMATION_CHANGED = 4096; // 0x1000
field public static final int ROTATION_ANIMATION_CROSSFADE = 1; // 0x1
field public static final int ROTATION_ANIMATION_JUMPCUT = 2; // 0x2
- field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
field public static final int ROTATION_ANIMATION_ROTATE = 0; // 0x0
+ field public static final int ROTATION_ANIMATION_SEAMLESS = 3; // 0x3
field public static final int SCREEN_BRIGHTNESS_CHANGED = 2048; // 0x800
field public static final int SCREEN_ORIENTATION_CHANGED = 1024; // 0x400
field public static final int SOFT_INPUT_ADJUST_NOTHING = 48; // 0x30
diff --git a/cmds/am/Android.mk b/cmds/am/Android.mk
index f8350dc..5586dd4 100644
--- a/cmds/am/Android.mk
+++ b/cmds/am/Android.mk
@@ -3,8 +3,11 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-proto-files-under, proto)
LOCAL_MODULE := am
+LOCAL_PROTOC_OPTIMIZE_TYPE := stream
include $(BUILD_JAVA_LIBRARY)
include $(CLEAR_VARS)
@@ -13,3 +16,14 @@
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_TAGS := optional
include $(BUILD_PREBUILT)
+
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+ $(call all-proto-files-under, proto)
+LOCAL_MODULE := libinstrumentation
+LOCAL_PROTOC_OPTIMIZE_TYPE := full
+LOCAL_EXPORT_C_INCLUDE_DIRS := \
+ $(call intermediates-dir-for,STATIC_LIBRARIES,libinstrumentation,HOST,,,)/proto/$(LOCAL_PATH)/proto
+include $(BUILD_HOST_STATIC_LIBRARY)
+
diff --git a/cmds/am/proto/instrumentation_data.proto b/cmds/am/proto/instrumentation_data.proto
new file mode 100644
index 0000000..12a18a2
--- /dev/null
+++ b/cmds/am/proto/instrumentation_data.proto
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+syntax = "proto2";
+package android.am;
+
+option java_package = "com.android.commands.am";
+
+message ResultsBundleEntry {
+ optional string key = 1;
+
+ optional string value_string = 2;
+ optional sint32 value_int = 3;
+ optional float value_float = 4;
+ optional double value_double = 5;
+ optional sint64 value_long = 6;
+ optional ResultsBundle value_bundle = 7;
+}
+
+message ResultsBundle {
+ repeated ResultsBundleEntry entries = 1;
+}
+
+message TestStatus {
+ optional sint32 result_code = 3;
+ optional ResultsBundle results = 4;
+}
+
+enum SessionStatusCode {
+ /**
+ * The command ran successfully. This does not imply that the tests passed.
+ */
+ SESSION_FINISHED = 0;
+
+ /**
+ * There was an unrecoverable error running the tests.
+ */
+ SESSION_ABORTED = 1;
+}
+
+message SessionStatus {
+ optional SessionStatusCode status_code = 1;
+ optional string error_text = 2;
+ optional sint32 result_code = 3;
+ optional ResultsBundle results = 4;
+}
+
+message Session {
+ repeated TestStatus test_status = 1;
+ optional SessionStatus session_status = 2;
+}
+
+
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index e197bfc..91a4549 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -1,20 +1,18 @@
/*
-**
-** Copyright 2007, 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.
-*/
-
+ * Copyright (C) 2007 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.commands.am;
@@ -235,6 +233,7 @@
" -e <NAME> <VALUE>: set argument <NAME> to <VALUE>. For test runners a\n" +
" common form is [-e <testrunner_flag> <value>[,<value>...]].\n" +
" -p <FILE>: write profiling data to <FILE>\n" +
+ " -m: Write output as protobuf (machine readable)\n" +
" -w: wait for instrumentation to finish before returning. Required for\n" +
" test runners.\n" +
" --user <USER_ID> | current: Specify user instrumentation runs in;\n" +
@@ -543,208 +542,43 @@
receiver.waitForFinish();
}
- private void runInstrument() throws Exception {
- String profileFile = null;
- boolean wait = false;
- boolean rawMode = false;
- boolean no_window_animation = false;
- int userId = UserHandle.USER_CURRENT;
- Bundle args = new Bundle();
- String argKey = null, argValue = null;
- IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
- String abi = null;
+ public void runInstrument() throws Exception {
+ Instrument instrument = new Instrument(mAm, mPm);
String opt;
while ((opt=nextOption()) != null) {
if (opt.equals("-p")) {
- profileFile = nextArgRequired();
+ instrument.profileFile = nextArgRequired();
} else if (opt.equals("-w")) {
- wait = true;
+ instrument.wait = true;
} else if (opt.equals("-r")) {
- rawMode = true;
+ instrument.rawMode = true;
+ } else if (opt.equals("-m")) {
+ instrument.proto = true;
} else if (opt.equals("-e")) {
- argKey = nextArgRequired();
- argValue = nextArgRequired();
- args.putString(argKey, argValue);
+ final String argKey = nextArgRequired();
+ final String argValue = nextArgRequired();
+ instrument.args.putString(argKey, argValue);
} else if (opt.equals("--no_window_animation")
|| opt.equals("--no-window-animation")) {
- no_window_animation = true;
+ instrument.noWindowAnimation = true;
} else if (opt.equals("--user")) {
- userId = parseUserArg(nextArgRequired());
+ instrument.userId = parseUserArg(nextArgRequired());
} else if (opt.equals("--abi")) {
- abi = nextArgRequired();
+ instrument.abi = nextArgRequired();
} else {
System.err.println("Error: Unknown option: " + opt);
return;
}
}
- if (userId == UserHandle.USER_ALL) {
+ if (instrument.userId == UserHandle.USER_ALL) {
System.err.println("Error: Can't start instrumentation with user 'all'");
return;
}
- String cnArg = nextArgRequired();
+ instrument.componentNameArg = nextArgRequired();
- ComponentName cn;
- if (cnArg.contains("/")) {
- cn = ComponentName.unflattenFromString(cnArg);
- if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
- } else {
- List<InstrumentationInfo> infos = mPm.queryInstrumentation(null, 0).getList();
-
- final int numInfos = infos == null ? 0: infos.size();
- List<ComponentName> cns = new ArrayList<>();
- for (int i = 0; i < numInfos; i++) {
- InstrumentationInfo info = infos.get(i);
-
- ComponentName c = new ComponentName(info.packageName, info.name);
- if (cnArg.equals(info.packageName)) {
- cns.add(c);
- }
- }
-
- if (cns.size() == 0) {
- throw new IllegalArgumentException("No instrumentation found for: " + cnArg);
- } else if (cns.size() == 1) {
- cn = cns.get(0);
- } else {
- StringBuilder cnsStr = new StringBuilder();
- final int numCns = cns.size();
- for (int i = 0; i < numCns; i++) {
- cnsStr.append(cns.get(i).flattenToString());
- cnsStr.append(", ");
- }
-
- // Remove last ", "
- cnsStr.setLength(cnsStr.length() - 2);
-
- throw new IllegalArgumentException("Found multiple instrumentations: "
- + cnsStr.toString());
- }
- }
-
- InstrumentationWatcher watcher = null;
- UiAutomationConnection connection = null;
- if (wait) {
- watcher = new InstrumentationWatcher();
- watcher.setRawOutput(rawMode);
- connection = new UiAutomationConnection();
- }
-
- float[] oldAnims = null;
- if (no_window_animation) {
- oldAnims = wm.getAnimationScales();
- wm.setAnimationScale(0, 0.0f);
- wm.setAnimationScale(1, 0.0f);
- }
-
- if (abi != null) {
- final String[] supportedAbis = Build.SUPPORTED_ABIS;
- boolean matched = false;
- for (String supportedAbi : supportedAbis) {
- if (supportedAbi.equals(abi)) {
- matched = true;
- break;
- }
- }
-
- if (!matched) {
- throw new AndroidException(
- "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi);
- }
- }
-
- if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, abi)) {
- throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
- }
-
- if (watcher != null) {
- if (!watcher.waitForFinish()) {
- System.out.println("INSTRUMENTATION_ABORTED: System has crashed.");
- }
- }
-
- if (oldAnims != null) {
- wm.setAnimationScales(oldAnims);
- }
- }
-
- private class InstrumentationWatcher extends IInstrumentationWatcher.Stub {
- private boolean mFinished = false;
- private boolean mRawMode = false;
-
- /**
- * Set or reset "raw mode". In "raw mode", all bundles are dumped. In "pretty mode",
- * if a bundle includes Instrumentation.REPORT_KEY_STREAMRESULT, just print that.
- * @param rawMode true for raw mode, false for pretty mode.
- */
- public void setRawOutput(boolean rawMode) {
- mRawMode = rawMode;
- }
-
- @Override
- public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) {
- synchronized (this) {
- // pretty printer mode?
- String pretty = null;
- if (!mRawMode && results != null) {
- pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
- }
- if (pretty != null) {
- System.out.print(pretty);
- } else {
- if (results != null) {
- for (String key : results.keySet()) {
- System.out.println(
- "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
- }
- }
- System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
- }
- notifyAll();
- }
- }
-
- @Override
- public void instrumentationFinished(ComponentName name, int resultCode,
- Bundle results) {
- synchronized (this) {
- // pretty printer mode?
- String pretty = null;
- if (!mRawMode && results != null) {
- pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
- }
- if (pretty != null) {
- System.out.println(pretty);
- } else {
- if (results != null) {
- for (String key : results.keySet()) {
- System.out.println(
- "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
- }
- }
- System.out.println("INSTRUMENTATION_CODE: " + resultCode);
- }
- mFinished = true;
- notifyAll();
- }
- }
-
- public boolean waitForFinish() {
- synchronized (this) {
- while (!mFinished) {
- try {
- if (!mAm.asBinder().pingBinder()) {
- return false;
- }
- wait(1000);
- } catch (InterruptedException e) {
- throw new IllegalStateException(e);
- }
- }
- }
- return true;
- }
+ instrument.run();
}
}
diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java
new file mode 100644
index 0000000..8eefd25
--- /dev/null
+++ b/cmds/am/src/com/android/commands/am/Instrument.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2007 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.commands.am;
+
+import android.app.IActivityManager;
+import android.app.IInstrumentationWatcher;
+import android.app.Instrumentation;
+import android.app.UiAutomationConnection;
+import android.content.ComponentName;
+import android.content.pm.IPackageManager;
+import android.content.pm.InstrumentationInfo;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.AndroidException;
+import android.util.proto.ProtoOutputStream;
+import android.view.IWindowManager;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Runs the am instrument command
+ */
+public class Instrument {
+ private final IActivityManager mAm;
+ private final IPackageManager mPm;
+ private final IWindowManager mWm;
+
+ // Command line arguments
+ public String profileFile = null;
+ public boolean wait = false;
+ public boolean rawMode = false;
+ public boolean proto = false;
+ public boolean noWindowAnimation = false;
+ public String abi = null;
+ public int userId = UserHandle.USER_CURRENT;
+ public Bundle args = new Bundle();
+ // Required
+ public String componentNameArg;
+
+ /**
+ * Construct the instrument command runner.
+ */
+ public Instrument(IActivityManager am, IPackageManager pm) {
+ mAm = am;
+ mPm = pm;
+ mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+ }
+
+ /**
+ * Base class for status reporting.
+ *
+ * All the methods on this interface are called within the synchronized block
+ * of the InstrumentationWatcher, so calls are in order. However, that means
+ * you must be careful not to do blocking operations because you don't know
+ * exactly the locking dependencies.
+ */
+ private interface StatusReporter {
+ /**
+ * Status update for tests.
+ */
+ public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
+ Bundle results);
+
+ /**
+ * The tests finished.
+ */
+ public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
+ Bundle results);
+
+ /**
+ * @param errorText a description of the error
+ * @param commandError True if the error is related to the commandline, as opposed
+ * to a test failing.
+ */
+ public void onError(String errorText, boolean commandError);
+ }
+
+ /**
+ * Printer for the 'classic' text based status reporting.
+ */
+ private class TextStatusReporter implements StatusReporter {
+ private boolean mRawMode;
+
+ /**
+ * Human-ish readable output.
+ *
+ * @param rawMode In "raw mode" (true), all bundles are dumped.
+ * In "pretty mode" (false), if a bundle includes
+ * Instrumentation.REPORT_KEY_STREAMRESULT, just print that.
+ */
+ public TextStatusReporter(boolean rawMode) {
+ mRawMode = rawMode;
+ }
+
+ @Override
+ public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
+ Bundle results) {
+ // pretty printer mode?
+ String pretty = null;
+ if (!mRawMode && results != null) {
+ pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
+ }
+ if (pretty != null) {
+ System.out.print(pretty);
+ } else {
+ if (results != null) {
+ for (String key : results.keySet()) {
+ System.out.println(
+ "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
+ }
+ }
+ System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
+ }
+ }
+
+ @Override
+ public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
+ Bundle results) {
+ // pretty printer mode?
+ String pretty = null;
+ if (!mRawMode && results != null) {
+ pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
+ }
+ if (pretty != null) {
+ System.out.println(pretty);
+ } else {
+ if (results != null) {
+ for (String key : results.keySet()) {
+ System.out.println(
+ "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
+ }
+ }
+ System.out.println("INSTRUMENTATION_CODE: " + resultCode);
+ }
+ }
+
+ @Override
+ public void onError(String errorText, boolean commandError) {
+ // The regular BaseCommand error printing will print the commandErrors.
+ if (!commandError) {
+ System.out.println(errorText);
+ }
+ }
+ }
+
+ /**
+ * Printer for the protobuf based status reporting.
+ */
+ private class ProtoStatusReporter implements StatusReporter {
+ @Override
+ public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
+ Bundle results) {
+ final ProtoOutputStream proto = new ProtoOutputStream();
+
+ final long token = proto.startRepeatedObject(InstrumentationData.Session.TEST_STATUS);
+
+ proto.writeSInt32(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
+ writeBundle(proto, InstrumentationData.TestStatus.RESULTS, results);
+
+ proto.endRepeatedObject(token);
+ writeProtoToStdout(proto);
+ }
+
+ @Override
+ public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
+ Bundle results) {
+ final ProtoOutputStream proto = new ProtoOutputStream();
+
+ final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
+
+ proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
+ InstrumentationData.SESSION_FINISHED);
+ proto.writeSInt32(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
+ writeBundle(proto, InstrumentationData.SessionStatus.RESULTS, results);
+
+ proto.endObject(token);
+ writeProtoToStdout(proto);
+ }
+
+ @Override
+ public void onError(String errorText, boolean commandError) {
+ final ProtoOutputStream proto = new ProtoOutputStream();
+
+ final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
+
+ proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
+ InstrumentationData.SESSION_ABORTED);
+ proto.writeString(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
+
+ proto.endObject(token);
+ writeProtoToStdout(proto);
+ }
+
+ private void writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle) {
+ final long bundleToken = proto.startObject(fieldId);
+
+ for (final String key: bundle.keySet()) {
+ final long entryToken = proto.startRepeatedObject(
+ InstrumentationData.ResultsBundle.ENTRIES);
+
+ proto.writeString(InstrumentationData.ResultsBundleEntry.KEY, key);
+
+ final Object val = bundle.get(key);
+ if (val instanceof String) {
+ proto.writeString(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
+ (String)val);
+ } else if (val instanceof Byte) {
+ proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
+ ((Byte)val).intValue());
+ } else if (val instanceof Double) {
+ proto.writeDouble(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE,
+ ((Double)val).doubleValue());
+ } else if (val instanceof Float) {
+ proto.writeFloat(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT,
+ ((Float)val).floatValue());
+ } else if (val instanceof Integer) {
+ proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
+ ((Integer)val).intValue());
+ } else if (val instanceof Long) {
+ proto.writeSInt64(InstrumentationData.ResultsBundleEntry.VALUE_LONG,
+ ((Long)val).longValue());
+ } else if (val instanceof Short) {
+ proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
+ ((Short)val).intValue());
+ } else if (val instanceof Bundle) {
+ writeBundle(proto, InstrumentationData.ResultsBundleEntry.VALUE_BUNDLE,
+ (Bundle)val);
+ }
+
+ proto.endRepeatedObject(entryToken);
+ }
+
+ proto.endObject(bundleToken);
+ }
+
+ private void writeProtoToStdout(ProtoOutputStream proto) {
+ try {
+ System.out.write(proto.getBytes());
+ System.out.flush();
+ } catch (IOException ex) {
+ System.err.println("Error writing finished response: ");
+ ex.printStackTrace(System.err);
+ }
+ }
+ }
+
+
+ /**
+ * Callbacks from the remote instrumentation instance.
+ */
+ private class InstrumentationWatcher extends IInstrumentationWatcher.Stub {
+ private final StatusReporter mReporter;
+
+ private boolean mFinished = false;
+
+ public InstrumentationWatcher(StatusReporter reporter) {
+ mReporter = reporter;
+ }
+
+ @Override
+ public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) {
+ synchronized (this) {
+ mReporter.onInstrumentationStatusLocked(name, resultCode, results);
+ notifyAll();
+ }
+ }
+
+ @Override
+ public void instrumentationFinished(ComponentName name, int resultCode, Bundle results) {
+ synchronized (this) {
+ mReporter.onInstrumentationFinishedLocked(name, resultCode, results);
+ mFinished = true;
+ notifyAll();
+ }
+ }
+
+ public boolean waitForFinish() {
+ synchronized (this) {
+ while (!mFinished) {
+ try {
+ if (!mAm.asBinder().pingBinder()) {
+ return false;
+ }
+ wait(1000);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Figure out which component they really meant.
+ */
+ private ComponentName parseComponentName(String cnArg) throws Exception {
+ if (cnArg.contains("/")) {
+ ComponentName cn = ComponentName.unflattenFromString(cnArg);
+ if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
+ return cn;
+ } else {
+ List<InstrumentationInfo> infos = mPm.queryInstrumentation(null, 0).getList();
+
+ final int numInfos = infos == null ? 0: infos.size();
+ ArrayList<ComponentName> cns = new ArrayList<>();
+ for (int i = 0; i < numInfos; i++) {
+ InstrumentationInfo info = infos.get(i);
+
+ ComponentName c = new ComponentName(info.packageName, info.name);
+ if (cnArg.equals(info.packageName)) {
+ cns.add(c);
+ }
+ }
+
+ if (cns.size() == 0) {
+ throw new IllegalArgumentException("No instrumentation found for: " + cnArg);
+ } else if (cns.size() == 1) {
+ return cns.get(0);
+ } else {
+ StringBuilder cnsStr = new StringBuilder();
+ final int numCns = cns.size();
+ for (int i = 0; i < numCns; i++) {
+ cnsStr.append(cns.get(i).flattenToString());
+ cnsStr.append(", ");
+ }
+
+ // Remove last ", "
+ cnsStr.setLength(cnsStr.length() - 2);
+
+ throw new IllegalArgumentException("Found multiple instrumentations: "
+ + cnsStr.toString());
+ }
+ }
+ }
+
+ /**
+ * Run the instrumentation.
+ */
+ public void run() throws Exception {
+ StatusReporter reporter = null;
+ float[] oldAnims = null;
+
+ try {
+ // Choose which output we will do.
+ if (proto) {
+ reporter = new ProtoStatusReporter();
+ } else if (wait) {
+ reporter = new TextStatusReporter(rawMode);
+ }
+
+ // Choose whether we have to wait for the results.
+ InstrumentationWatcher watcher = null;
+ UiAutomationConnection connection = null;
+ if (reporter != null) {
+ watcher = new InstrumentationWatcher(reporter);
+ connection = new UiAutomationConnection();
+ }
+
+ // Set the window animation if necessary
+ if (noWindowAnimation) {
+ oldAnims = mWm.getAnimationScales();
+ mWm.setAnimationScale(0, 0.0f);
+ mWm.setAnimationScale(1, 0.0f);
+ }
+
+ // Figure out which component we are tring to do.
+ final ComponentName cn = parseComponentName(componentNameArg);
+
+ // Choose an ABI if necessary
+ if (abi != null) {
+ final String[] supportedAbis = Build.SUPPORTED_ABIS;
+ boolean matched = false;
+ for (String supportedAbi : supportedAbis) {
+ if (supportedAbi.equals(abi)) {
+ matched = true;
+ break;
+ }
+ }
+ if (!matched) {
+ throw new AndroidException(
+ "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi);
+ }
+ }
+
+ // Start the instrumentation
+ if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId,
+ abi)) {
+ throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
+ }
+
+ // If we have been requested to wait, do so until the instrumentation is finished.
+ if (watcher != null) {
+ if (!watcher.waitForFinish()) {
+ reporter.onError("INSTRUMENTATION_ABORTED: System has crashed.", false);
+ return;
+ }
+ }
+ } catch (Exception ex) {
+ // Report failures
+ if (reporter != null) {
+ reporter.onError(ex.getMessage(), true);
+ }
+
+ // And re-throw the exception
+ throw ex;
+ } finally {
+ // Clean up
+ if (oldAnims != null) {
+ mWm.setAnimationScales(oldAnims);
+ }
+ }
+ }
+}
+
diff --git a/cmds/webview_zygote/Android.mk b/cmds/webview_zygote/Android.mk
new file mode 100644
index 0000000..66e762c
--- /dev/null
+++ b/cmds/webview_zygote/Android.mk
@@ -0,0 +1,49 @@
+#
+# Copyright (C) 2016 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := webview_zygote
+
+LOCAL_SRC_FILES := webview_zygote.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libandroid_runtime \
+ libbinder \
+ liblog \
+ libcutils \
+ libutils
+
+LOCAL_LDFLAGS_32 := -Wl,--version-script,art/sigchainlib/version-script32.txt -Wl,--export-dynamic
+LOCAL_LDFLAGS_64 := -Wl,--version-script,art/sigchainlib/version-script64.txt -Wl,--export-dynamic
+
+LOCAL_WHOLE_STATIC_LIBRARIES := libsigchain
+
+LOCAL_INIT_RC := webview_zygote32.rc
+
+# Always include the 32-bit version of webview_zygote. If the target is 64-bit,
+# also include the 64-bit webview_zygote.
+ifeq ($(TARGET_SUPPORTS_64_BIT_APPS),true)
+ LOCAL_INIT_RC += webview_zygote64.rc
+endif
+
+LOCAL_MULTILIB := both
+
+LOCAL_MODULE_STEM_32 := webview_zygote32
+LOCAL_MODULE_STEM_64 := webview_zygote64
+
+include $(BUILD_EXECUTABLE)
diff --git a/cmds/webview_zygote/webview_zygote.cpp b/cmds/webview_zygote/webview_zygote.cpp
new file mode 100644
index 0000000..88fee64
--- /dev/null
+++ b/cmds/webview_zygote/webview_zygote.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+
+#define LOG_TAG "WebViewZygote"
+
+#include <sys/prctl.h>
+
+#include <android_runtime/AndroidRuntime.h>
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+class WebViewRuntime : public AndroidRuntime {
+public:
+ WebViewRuntime(char* argBlockStart, size_t argBlockSize)
+ : AndroidRuntime(argBlockStart, argBlockSize) {}
+
+ ~WebViewRuntime() override {}
+
+ void onStarted() override {
+ // Nothing to do since this is a zygote server.
+ }
+
+ void onVmCreated(JNIEnv*) override {
+ // Nothing to do when the VM is created in the zygote.
+ }
+
+ void onZygoteInit() override {
+ // Called after a new process is forked.
+ sp<ProcessState> proc = ProcessState::self();
+ proc->startThreadPool();
+ }
+
+ void onExit(int code) override {
+ IPCThreadState::self()->stopProcess();
+ AndroidRuntime::onExit(code);
+ }
+};
+
+} // namespace android
+
+int main(int argc, char* const argv[]) {
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
+ LOG_ALWAYS_FATAL("PR_SET_NO_NEW_PRIVS failed: %s", strerror(errno));
+ return 12;
+ }
+
+ size_t argBlockSize = 0;
+ for (int i = 0; i < argc; ++i) {
+ argBlockSize += strlen(argv[i]) + 1;
+ }
+
+ android::WebViewRuntime runtime(argv[0], argBlockSize);
+ runtime.addOption("-Xzygote");
+
+ android::Vector<android::String8> args;
+ runtime.start("com.android.internal.os.WebViewZygoteInit", args, /*zygote=*/ true);
+}
diff --git a/cmds/webview_zygote/webview_zygote32.rc b/cmds/webview_zygote/webview_zygote32.rc
new file mode 100644
index 0000000..b7decc8
--- /dev/null
+++ b/cmds/webview_zygote/webview_zygote32.rc
@@ -0,0 +1,22 @@
+#
+# Copyright (C) 2016 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.
+#
+
+service webview_zygote32 /system/bin/webview_zygote32
+ user webview_zygote
+ socket webview_zygote stream 660 webview_zygote system
+
+on property:init.svc.zygote=stopped
+ stop webview_zygote32
diff --git a/cmds/webview_zygote/webview_zygote64.rc b/cmds/webview_zygote/webview_zygote64.rc
new file mode 100644
index 0000000..2935b28
--- /dev/null
+++ b/cmds/webview_zygote/webview_zygote64.rc
@@ -0,0 +1,22 @@
+#
+# Copyright (C) 2016 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.
+#
+
+service webview_zygote64 /system/bin/webview_zygote64
+ user webview_zygote
+ socket webview_zygote stream 660 webview_zygote system
+
+on property:init.svc.zygote=stopped
+ stop webview_zygote64
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 0d9be5f..4066f1c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3045,6 +3045,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
public int getPackageImportance(String packageName) {
try {
int procState = ActivityManagerNative.getDefault().getPackageProcessState(packageName,
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index d91472b..4df1325 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -150,7 +150,7 @@
{
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
String callingPackage = data.readString();
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
@@ -173,7 +173,7 @@
{
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
String callingPackage = data.readString();
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
@@ -197,7 +197,7 @@
{
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
String callingPackage = data.readString();
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
@@ -223,7 +223,7 @@
{
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
String callingPackage = data.readString();
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
@@ -247,7 +247,7 @@
{
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
String callingPackage = data.readString();
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
@@ -270,7 +270,7 @@
{
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
IntentSender intent = IntentSender.CREATOR.createFromParcel(data);
Intent fillInIntent = null;
if (data.readInt() != 0) {
@@ -432,7 +432,7 @@
case RELEASE_SOME_ACTIVITIES_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
- IApplicationThread app = ApplicationThreadNative.asInterface(data.readStrongBinder());
+ IApplicationThread app = IApplicationThread.Stub.asInterface(data.readStrongBinder());
releaseSomeActivities(app);
reply.writeNoException();
return true;
@@ -452,7 +452,7 @@
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app =
- b != null ? ApplicationThreadNative.asInterface(b) : null;
+ b != null ? IApplicationThread.Stub.asInterface(b) : null;
String packageName = data.readString();
b = data.readStrongBinder();
IIntentReceiver rec
@@ -489,7 +489,7 @@
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app =
- b != null ? ApplicationThreadNative.asInterface(b) : null;
+ b != null ? IApplicationThread.Stub.asInterface(b) : null;
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
b = data.readStrongBinder();
@@ -516,7 +516,7 @@
{
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = b != null ? ApplicationThreadNative.asInterface(b) : null;
+ IApplicationThread app = b != null ? IApplicationThread.Stub.asInterface(b) : null;
Intent intent = Intent.CREATOR.createFromParcel(data);
int userId = data.readInt();
unbroadcastIntent(app, intent, userId);
@@ -541,7 +541,7 @@
case ATTACH_APPLICATION_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
- IApplicationThread app = ApplicationThreadNative.asInterface(
+ IApplicationThread app = IApplicationThread.Stub.asInterface(
data.readStrongBinder());
if (app != null) {
attachApplication(app);
@@ -978,7 +978,7 @@
case GET_CONTENT_PROVIDER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
String name = data.readString();
int userId = data.readInt();
boolean stable = data.readInt() != 0;
@@ -1012,7 +1012,7 @@
case PUBLISH_CONTENT_PROVIDERS_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
ArrayList<ContentProviderHolder> providers =
data.createTypedArrayList(ContentProviderHolder.CREATOR);
publishContentProviders(app, providers);
@@ -1077,7 +1077,7 @@
case START_SERVICE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
Intent service = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
String callingPackage = data.readString();
@@ -1091,7 +1091,7 @@
case STOP_SERVICE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
Intent service = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
int userId = data.readInt();
@@ -1130,7 +1130,7 @@
case BIND_SERVICE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
IBinder token = data.readStrongBinder();
Intent service = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
@@ -1210,7 +1210,7 @@
case FINISH_INSTRUMENTATION_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
int resultCode = data.readInt();
Bundle results = data.readBundle();
finishInstrumentation(app, resultCode, results);
@@ -1235,6 +1235,16 @@
return true;
}
+ case UPDATE_DISPLAY_OVERRIDE_CONFIGURATION_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final Configuration config = Configuration.CREATOR.createFromParcel(data);
+ final int displayId = data.readInt();
+ final boolean updated = updateDisplayOverrideConfiguration(config, displayId);
+ reply.writeNoException();
+ reply.writeInt(updated ? 1 : 0);
+ return true;
+ }
+
case SET_REQUESTED_ORIENTATION_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -1421,7 +1431,7 @@
case GRANT_URI_PERMISSION_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
String targetPkg = data.readString();
Uri uri = Uri.CREATOR.createFromParcel(data);
int mode = data.readInt();
@@ -1434,7 +1444,7 @@
case REVOKE_URI_PERMISSION_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
Uri uri = Uri.CREATOR.createFromParcel(data);
int mode = data.readInt();
int userId = data.readInt();
@@ -1497,7 +1507,7 @@
case SHOW_WAITING_FOR_DEBUGGER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
boolean waiting = data.readInt() != 0;
showWaitingForDebugger(app, waiting);
reply.writeNoException();
@@ -2061,7 +2071,7 @@
{
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ IApplicationThread app = IApplicationThread.Stub.asInterface(b);
String callingPackage = data.readString();
Intent[] intents = data.createTypedArray(Intent.CREATOR);
String[] resolvedTypes = data.createStringArray();
@@ -2931,6 +2941,20 @@
reply.writeNoException();
return true;
}
+ case GET_DEFAULT_PICTURE_IN_PICTURE_BOUNDS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ Rect r = getDefaultPictureInPictureBounds();
+ reply.writeNoException();
+ r.writeToParcel(reply, 0);
+ return true;
+ }
+ case GET_PICTURE_IN_PICTURE_MOVEMENT_BOUNDS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ Rect r = getPictureInPictureMovementBounds();
+ reply.writeNoException();
+ r.writeToParcel(reply, 0);
+ return true;
+ }
case SET_VR_MODE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
final IBinder token = data.readStrongBinder();
@@ -4594,8 +4618,7 @@
data.recycle();
return res;
}
- public boolean updateConfiguration(Configuration values) throws RemoteException
- {
+ public boolean updateConfiguration(Configuration values) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -4607,6 +4630,20 @@
reply.recycle();
return updated;
}
+ public boolean updateDisplayOverrideConfiguration(Configuration values, int displayId)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ values.writeToParcel(data, 0);
+ data.writeInt(displayId);
+ mRemote.transact(UPDATE_DISPLAY_OVERRIDE_CONFIGURATION_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean updated = reply.readInt() == 1;
+ data.recycle();
+ reply.recycle();
+ return updated;
+ }
public void setRequestedOrientation(IBinder token, int requestedOrientation)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -6989,6 +7026,34 @@
}
@Override
+ public Rect getDefaultPictureInPictureBounds() throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(GET_DEFAULT_PICTURE_IN_PICTURE_BOUNDS_TRANSACTION, data, reply, 0);
+ reply.readException();
+ Rect rect = Rect.CREATOR.createFromParcel(reply);
+ data.recycle();
+ reply.recycle();
+ return rect;
+ }
+
+ @Override
+ public Rect getPictureInPictureMovementBounds() throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(GET_PICTURE_IN_PICTURE_MOVEMENT_BOUNDS_TRANSACTION, data, reply, 0);
+ reply.readException();
+ Rect rect = Rect.CREATOR.createFromParcel(reply);
+ data.recycle();
+ reply.recycle();
+ return rect;
+ }
+
+ @Override
public boolean isAppForeground(int uid) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3a8b6c7..e9a200f 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -647,7 +647,7 @@
private native void dumpGraphicsInfo(FileDescriptor fd);
- private class ApplicationThread extends ApplicationThreadNative {
+ private class ApplicationThread extends IApplicationThread.Stub {
private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s";
private int mLastProcessState = -1;
@@ -859,7 +859,7 @@
IUiAutomationConnection instrumentationUiConnection, int debugMode,
boolean enableBinderTracking, boolean trackAllocation,
boolean isRestrictedBackupMode, boolean persistent, Configuration config,
- CompatibilityInfo compatInfo, Map<String, IBinder> services, Bundle coreSettings,
+ CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
String buildSerial) {
if (services != null) {
@@ -929,15 +929,17 @@
mH.sendMessage(mH.obtainMessage(H.GC_WHEN_IDLE));
}
- public void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args) {
+ public void dumpService(ParcelFileDescriptor pfd, IBinder servicetoken, String[] args) {
DumpComponentInfo data = new DumpComponentInfo();
try {
- data.fd = ParcelFileDescriptor.dup(fd);
+ data.fd = pfd.dup();
data.token = servicetoken;
data.args = args;
sendMessage(H.DUMP_SERVICE, data, 0, 0, true /*async*/);
} catch (IOException e) {
Slog.w(TAG, "dumpService failed", e);
+ } finally {
+ IoUtils.closeQuietly(pfd);
}
}
@@ -976,6 +978,10 @@
sendMessage(H.DUMP_HEAP, dhd, managed ? 1 : 0, 0, true /*async*/);
}
+ public void attachAgent(String agent) {
+ sendMessage(H.ATTACH_AGENT, agent);
+ }
+
public void setSchedulingGroup(int group) {
// Note: do this immediately, since going into the foreground
// should happen regardless of what pending work we have to do
@@ -996,43 +1002,48 @@
sendMessage(H.SCHEDULE_CRASH, msg);
}
- public void dumpActivity(FileDescriptor fd, IBinder activitytoken,
+ public void dumpActivity(ParcelFileDescriptor pfd, IBinder activitytoken,
String prefix, String[] args) {
DumpComponentInfo data = new DumpComponentInfo();
try {
- data.fd = ParcelFileDescriptor.dup(fd);
+ data.fd = pfd.dup();
data.token = activitytoken;
data.prefix = prefix;
data.args = args;
sendMessage(H.DUMP_ACTIVITY, data, 0, 0, true /*async*/);
} catch (IOException e) {
Slog.w(TAG, "dumpActivity failed", e);
+ } finally {
+ IoUtils.closeQuietly(pfd);
}
}
- public void dumpProvider(FileDescriptor fd, IBinder providertoken,
+ public void dumpProvider(ParcelFileDescriptor pfd, IBinder providertoken,
String[] args) {
DumpComponentInfo data = new DumpComponentInfo();
try {
- data.fd = ParcelFileDescriptor.dup(fd);
+ data.fd = pfd.dup();
data.token = providertoken;
data.args = args;
sendMessage(H.DUMP_PROVIDER, data, 0, 0, true /*async*/);
} catch (IOException e) {
Slog.w(TAG, "dumpProvider failed", e);
+ } finally {
+ IoUtils.closeQuietly(pfd);
}
}
@Override
- public void dumpMemInfo(FileDescriptor fd, Debug.MemoryInfo mem, boolean checkin,
+ public void dumpMemInfo(ParcelFileDescriptor pfd, Debug.MemoryInfo mem, boolean checkin,
boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
boolean dumpUnreachable, String[] args) {
- FileOutputStream fout = new FileOutputStream(fd);
+ FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor());
PrintWriter pw = new FastPrintWriter(fout);
try {
dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly, dumpUnreachable);
} finally {
pw.flush();
+ IoUtils.closeQuietly(pfd);
}
}
@@ -1175,44 +1186,49 @@
}
@Override
- public void dumpGfxInfo(FileDescriptor fd, String[] args) {
- dumpGraphicsInfo(fd);
- WindowManagerGlobal.getInstance().dumpGfxInfo(fd, args);
+ public void dumpGfxInfo(ParcelFileDescriptor pfd, String[] args) {
+ dumpGraphicsInfo(pfd.getFileDescriptor());
+ WindowManagerGlobal.getInstance().dumpGfxInfo(pfd.getFileDescriptor(), args);
+ IoUtils.closeQuietly(pfd);
}
- private void dumpDatabaseInfo(FileDescriptor fd, String[] args) {
- PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd));
+ private void dumpDatabaseInfo(ParcelFileDescriptor pfd, String[] args) {
+ PrintWriter pw = new FastPrintWriter(
+ new FileOutputStream(pfd.getFileDescriptor()));
PrintWriterPrinter printer = new PrintWriterPrinter(pw);
SQLiteDebug.dump(printer, args);
pw.flush();
}
@Override
- public void dumpDbInfo(final FileDescriptor fd, final String[] args) {
+ public void dumpDbInfo(final ParcelFileDescriptor pfd, final String[] args) {
if (mSystemThread) {
// Ensure this invocation is asynchronous to prevent writer waiting if buffer cannot
// be consumed. But it must duplicate the file descriptor first, since caller might
// be closing it.
final ParcelFileDescriptor dup;
try {
- dup = ParcelFileDescriptor.dup(fd);
+ dup = pfd.dup();
} catch (IOException e) {
- Log.w(TAG, "Could not dup FD " + fd.getInt$());
+ Log.w(TAG, "Could not dup FD " + pfd.getFileDescriptor().getInt$());
return;
+ } finally {
+ IoUtils.closeQuietly(pfd);
}
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
try {
- dumpDatabaseInfo(dup.getFileDescriptor(), args);
+ dumpDatabaseInfo(dup, args);
} finally {
IoUtils.closeQuietly(dup);
}
}
});
} else {
- dumpDatabaseInfo(fd, args);
+ dumpDatabaseInfo(pfd, args);
+ IoUtils.closeQuietly(pfd);
}
}
@@ -1251,9 +1267,9 @@
sendMessage(H.TRANSLUCENT_CONVERSION_COMPLETE, token, drawComplete ? 1 : 0);
}
- public void scheduleOnNewActivityOptions(IBinder token, ActivityOptions options) {
+ public void scheduleOnNewActivityOptions(IBinder token, Bundle options) {
sendMessage(H.ON_NEW_ACTIVITY_OPTIONS,
- new Pair<IBinder, ActivityOptions>(token, options));
+ new Pair<IBinder, ActivityOptions>(token, ActivityOptions.fromBundle(options)));
}
public void setProcessState(int state) {
@@ -1319,10 +1335,12 @@
}
@Override
- public void stopBinderTrackingAndDump(FileDescriptor fd) {
+ public void stopBinderTrackingAndDump(ParcelFileDescriptor pfd) {
try {
- sendMessage(H.STOP_BINDER_TRACKING_AND_DUMP, ParcelFileDescriptor.dup(fd));
+ sendMessage(H.STOP_BINDER_TRACKING_AND_DUMP, pfd.dup());
} catch (IOException e) {
+ } finally {
+ IoUtils.closeQuietly(pfd);
}
}
@@ -1415,6 +1433,7 @@
public static final int MULTI_WINDOW_MODE_CHANGED = 152;
public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153;
public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
+ public static final int ATTACH_AGENT = 155;
String codeToString(int code) {
if (DEBUG_MESSAGES) {
@@ -1471,6 +1490,7 @@
case MULTI_WINDOW_MODE_CHANGED: return "MULTI_WINDOW_MODE_CHANGED";
case PICTURE_IN_PICTURE_MODE_CHANGED: return "PICTURE_IN_PICTURE_MODE_CHANGED";
case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
+ case ATTACH_AGENT: return "ATTACH_AGENT";
}
}
return Integer.toString(code);
@@ -1725,6 +1745,8 @@
case LOCAL_VOICE_INTERACTION_STARTED:
handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
(IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
+ case ATTACH_AGENT:
+ handleAttachAgent((String) msg.obj);
break;
}
Object obj = msg.obj;
@@ -2994,6 +3016,14 @@
}
}
+ static final void handleAttachAgent(String agent) {
+ try {
+ VMDebug.attachAgent(agent);
+ } catch (IOException e) {
+ Slog.e(TAG, "Attaching agent failed: " + agent);
+ }
+ }
+
private static final ThreadLocal<Intent> sCurrentBroadcastIntent = new ThreadLocal<Intent>();
/**
@@ -3097,8 +3127,8 @@
String classname = data.appInfo.backupAgentName;
// full backup operation but no app-supplied agent? use the default implementation
- if (classname == null && (data.backupMode == IApplicationThread.BACKUP_MODE_FULL
- || data.backupMode == IApplicationThread.BACKUP_MODE_RESTORE_FULL)) {
+ if (classname == null && (data.backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL
+ || data.backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL)) {
classname = "android.app.backup.FullBackupAgent";
}
@@ -3130,8 +3160,9 @@
// If this is during restore, fail silently; otherwise go
// ahead and let the user see the crash.
Slog.e(TAG, "Agent threw during creation: " + e);
- if (data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE
- && data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE_FULL) {
+ if (data.backupMode != ApplicationThreadConstants.BACKUP_MODE_RESTORE
+ && data.backupMode !=
+ ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL) {
throw e;
}
// falling through with 'binder' still null
@@ -4904,10 +4935,10 @@
final void handleDispatchPackageBroadcast(int cmd, String[] packages) {
boolean hasPkgInfo = false;
switch (cmd) {
- case IApplicationThread.PACKAGE_REMOVED:
- case IApplicationThread.PACKAGE_REMOVED_DONT_KILL:
+ case ApplicationThreadConstants.PACKAGE_REMOVED:
+ case ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL:
{
- final boolean killApp = cmd == IApplicationThread.PACKAGE_REMOVED;
+ final boolean killApp = cmd == ApplicationThreadConstants.PACKAGE_REMOVED;
if (packages == null) {
break;
}
@@ -4932,7 +4963,7 @@
}
break;
}
- case IApplicationThread.PACKAGE_REPLACED:
+ case ApplicationThreadConstants.PACKAGE_REPLACED:
{
if (packages == null) {
break;
@@ -5232,10 +5263,10 @@
/* ignore */
}
- if (data.debugMode != IApplicationThread.DEBUG_OFF) {
+ if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
// XXX should have option to change the port.
Debug.changeDebugPort(8100);
- if (data.debugMode == IApplicationThread.DEBUG_WAIT) {
+ if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) {
Slog.w(TAG, "Application " + data.info.getPackageName()
+ " is waiting for the debugger on port 8100...");
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 37faa2e..3b3e070 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1381,7 +1381,7 @@
static void handlePackageBroadcast(int cmd, String[] pkgList, boolean hasPkgInfo) {
boolean immediateGc = false;
- if (cmd == IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE) {
+ if (cmd == ApplicationThreadConstants.EXTERNAL_STORAGE_UNAVAILABLE) {
immediateGc = true;
}
if (pkgList != null && (pkgList.length > 0)) {
diff --git a/core/java/android/app/ApplicationThreadConstants.java b/core/java/android/app/ApplicationThreadConstants.java
new file mode 100644
index 0000000..1fa670f
--- /dev/null
+++ b/core/java/android/app/ApplicationThreadConstants.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 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 android.app;
+
+/**
+ * @hide
+ */
+public final class ApplicationThreadConstants {
+ public static final int BACKUP_MODE_INCREMENTAL = 0;
+ public static final int BACKUP_MODE_FULL = 1;
+ public static final int BACKUP_MODE_RESTORE = 2;
+ public static final int BACKUP_MODE_RESTORE_FULL = 3;
+
+ public static final int DEBUG_OFF = 0;
+ public static final int DEBUG_ON = 1;
+ public static final int DEBUG_WAIT = 2;
+
+ // the package has been removed, clean up internal references
+ public static final int PACKAGE_REMOVED = 0;
+ public static final int EXTERNAL_STORAGE_UNAVAILABLE = 1;
+ // the package is being modified in-place, don't kill it and retain references to it
+ public static final int PACKAGE_REMOVED_DONT_KILL = 2;
+ // a previously removed package was replaced with a new version [eg. upgrade, split added, ...]
+ public static final int PACKAGE_REPLACED = 3;
+}
\ No newline at end of file
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
deleted file mode 100644
index 12e527e..0000000
--- a/core/java/android/app/ApplicationThreadNative.java
+++ /dev/null
@@ -1,1544 +0,0 @@
-/*
- * Copyright (C) 2006 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 android.app;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.IIntentReceiver;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.ProviderInfo;
-import android.content.pm.ServiceInfo;
-import android.content.res.CompatibilityInfo;
-import android.content.res.Configuration;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Debug;
-import android.os.Parcelable;
-import android.os.PersistableBundle;
-import android.os.RemoteException;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
-import android.os.TransactionTooLargeException;
-import android.util.Log;
-
-import com.android.internal.app.IVoiceInteractor;
-import com.android.internal.content.ReferrerIntent;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/** {@hide} */
-public abstract class ApplicationThreadNative extends Binder
- implements IApplicationThread {
- /**
- * Cast a Binder object into an application thread interface, generating
- * a proxy if needed.
- */
- static public IApplicationThread asInterface(IBinder obj) {
- if (obj == null) {
- return null;
- }
- IApplicationThread in =
- (IApplicationThread)obj.queryLocalInterface(descriptor);
- if (in != null) {
- return in;
- }
-
- return new ApplicationThreadProxy(obj);
- }
-
- public ApplicationThreadNative() {
- attachInterface(this, descriptor);
- }
-
- @Override
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- switch (code) {
- case SCHEDULE_PAUSE_ACTIVITY_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder b = data.readStrongBinder();
- boolean finished = data.readInt() != 0;
- boolean userLeaving = data.readInt() != 0;
- int configChanges = data.readInt();
- boolean dontReport = data.readInt() != 0;
- schedulePauseActivity(b, finished, userLeaving, configChanges, dontReport);
- return true;
- }
-
- case SCHEDULE_STOP_ACTIVITY_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder b = data.readStrongBinder();
- boolean show = data.readInt() != 0;
- int configChanges = data.readInt();
- scheduleStopActivity(b, show, configChanges);
- return true;
- }
-
- case SCHEDULE_WINDOW_VISIBILITY_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder b = data.readStrongBinder();
- boolean show = data.readInt() != 0;
- scheduleWindowVisibility(b, show);
- return true;
- }
-
- case SCHEDULE_SLEEPING_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder b = data.readStrongBinder();
- boolean sleeping = data.readInt() != 0;
- scheduleSleeping(b, sleeping);
- return true;
- }
-
- case SCHEDULE_RESUME_ACTIVITY_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder b = data.readStrongBinder();
- int procState = data.readInt();
- boolean isForward = data.readInt() != 0;
- Bundle resumeArgs = data.readBundle();
- scheduleResumeActivity(b, procState, isForward, resumeArgs);
- return true;
- }
-
- case SCHEDULE_SEND_RESULT_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder b = data.readStrongBinder();
- List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
- scheduleSendResult(b, ri);
- return true;
- }
-
- case SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- Intent intent = Intent.CREATOR.createFromParcel(data);
- IBinder b = data.readStrongBinder();
- int ident = data.readInt();
- ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
- Configuration curConfig = Configuration.CREATOR.createFromParcel(data);
- Configuration overrideConfig = null;
- if (data.readInt() != 0) {
- overrideConfig = Configuration.CREATOR.createFromParcel(data);
- }
- CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
- String referrer = data.readString();
- IVoiceInteractor voiceInteractor = IVoiceInteractor.Stub.asInterface(
- data.readStrongBinder());
- int procState = data.readInt();
- Bundle state = data.readBundle();
- PersistableBundle persistentState = data.readPersistableBundle();
- List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
- List<ReferrerIntent> pi = data.createTypedArrayList(ReferrerIntent.CREATOR);
- boolean notResumed = data.readInt() != 0;
- boolean isForward = data.readInt() != 0;
- ProfilerInfo profilerInfo = data.readInt() != 0
- ? ProfilerInfo.CREATOR.createFromParcel(data) : null;
- scheduleLaunchActivity(intent, b, ident, info, curConfig, overrideConfig, compatInfo,
- referrer, voiceInteractor, procState, state, persistentState, ri, pi,
- notResumed, isForward, profilerInfo);
- return true;
- }
-
- case SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder b = data.readStrongBinder();
- List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
- List<ReferrerIntent> pi = data.createTypedArrayList(ReferrerIntent.CREATOR);
- int configChanges = data.readInt();
- boolean notResumed = data.readInt() != 0;
- Configuration config = Configuration.CREATOR.createFromParcel(data);
- Configuration overrideConfig = null;
- if (data.readInt() != 0) {
- overrideConfig = Configuration.CREATOR.createFromParcel(data);
- }
- boolean preserveWindows = data.readInt() == 1;
- scheduleRelaunchActivity(b, ri, pi, configChanges, notResumed, config, overrideConfig,
- preserveWindows);
- return true;
- }
-
- case SCHEDULE_NEW_INTENT_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- List<ReferrerIntent> pi = data.createTypedArrayList(ReferrerIntent.CREATOR);
- IBinder b = data.readStrongBinder();
- final boolean andPause = data.readInt() == 1;
- scheduleNewIntent(pi, b, andPause);
- return true;
- }
-
- case SCHEDULE_FINISH_ACTIVITY_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder b = data.readStrongBinder();
- boolean finishing = data.readInt() != 0;
- int configChanges = data.readInt();
- scheduleDestroyActivity(b, finishing, configChanges);
- return true;
- }
-
- case SCHEDULE_RECEIVER_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- Intent intent = Intent.CREATOR.createFromParcel(data);
- ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
- CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
- int resultCode = data.readInt();
- String resultData = data.readString();
- Bundle resultExtras = data.readBundle();
- boolean sync = data.readInt() != 0;
- int sendingUser = data.readInt();
- int processState = data.readInt();
- scheduleReceiver(intent, info, compatInfo, resultCode, resultData,
- resultExtras, sync, sendingUser, processState);
- return true;
- }
-
- case SCHEDULE_CREATE_SERVICE_TRANSACTION: {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder token = data.readStrongBinder();
- ServiceInfo info = ServiceInfo.CREATOR.createFromParcel(data);
- CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
- int processState = data.readInt();
- scheduleCreateService(token, info, compatInfo, processState);
- return true;
- }
-
- case SCHEDULE_BIND_SERVICE_TRANSACTION: {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder token = data.readStrongBinder();
- Intent intent = Intent.CREATOR.createFromParcel(data);
- boolean rebind = data.readInt() != 0;
- int processState = data.readInt();
- scheduleBindService(token, intent, rebind, processState);
- return true;
- }
-
- case SCHEDULE_UNBIND_SERVICE_TRANSACTION: {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder token = data.readStrongBinder();
- Intent intent = Intent.CREATOR.createFromParcel(data);
- scheduleUnbindService(token, intent);
- return true;
- }
-
- case SCHEDULE_SERVICE_ARGS_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder token = data.readStrongBinder();
- boolean taskRemoved = data.readInt() != 0;
- int startId = data.readInt();
- int fl = data.readInt();
- Intent args;
- if (data.readInt() != 0) {
- args = Intent.CREATOR.createFromParcel(data);
- } else {
- args = null;
- }
- scheduleServiceArgs(token, taskRemoved, startId, fl, args);
- return true;
- }
-
- case SCHEDULE_STOP_SERVICE_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder token = data.readStrongBinder();
- scheduleStopService(token);
- return true;
- }
-
- case BIND_APPLICATION_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- String packageName = data.readString();
- ApplicationInfo info =
- ApplicationInfo.CREATOR.createFromParcel(data);
- List<ProviderInfo> providers =
- data.createTypedArrayList(ProviderInfo.CREATOR);
- ComponentName testName = (data.readInt() != 0)
- ? new ComponentName(data) : null;
- ProfilerInfo profilerInfo = data.readInt() != 0
- ? ProfilerInfo.CREATOR.createFromParcel(data) : null;
- Bundle testArgs = data.readBundle();
- IBinder binder = data.readStrongBinder();
- IInstrumentationWatcher testWatcher = IInstrumentationWatcher.Stub.asInterface(binder);
- binder = data.readStrongBinder();
- IUiAutomationConnection uiAutomationConnection =
- IUiAutomationConnection.Stub.asInterface(binder);
- int testMode = data.readInt();
- boolean enableBinderTracking = data.readInt() != 0;
- boolean trackAllocation = data.readInt() != 0;
- boolean restrictedBackupMode = (data.readInt() != 0);
- boolean persistent = (data.readInt() != 0);
- Configuration config = Configuration.CREATOR.createFromParcel(data);
- CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
- HashMap<String, IBinder> services = data.readHashMap(null);
- Bundle coreSettings = data.readBundle();
- String buildSerial = data.readString();
- bindApplication(packageName, info, providers, testName, profilerInfo, testArgs,
- testWatcher, uiAutomationConnection, testMode, enableBinderTracking,
- trackAllocation, restrictedBackupMode, persistent, config, compatInfo, services,
- coreSettings, buildSerial);
- return true;
- }
-
- case SCHEDULE_EXIT_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- scheduleExit();
- return true;
- }
-
- case SCHEDULE_SUICIDE_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- scheduleSuicide();
- return true;
- }
-
- case SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- Configuration config = Configuration.CREATOR.createFromParcel(data);
- scheduleConfigurationChanged(config);
- return true;
- }
-
- case UPDATE_TIME_ZONE_TRANSACTION: {
- data.enforceInterface(IApplicationThread.descriptor);
- updateTimeZone();
- return true;
- }
-
- case CLEAR_DNS_CACHE_TRANSACTION: {
- data.enforceInterface(IApplicationThread.descriptor);
- clearDnsCache();
- return true;
- }
-
- case SET_HTTP_PROXY_TRANSACTION: {
- data.enforceInterface(IApplicationThread.descriptor);
- final String proxy = data.readString();
- final String port = data.readString();
- final String exclList = data.readString();
- final Uri pacFileUrl = Uri.CREATOR.createFromParcel(data);
- setHttpProxy(proxy, port, exclList, pacFileUrl);
- return true;
- }
-
- case PROCESS_IN_BACKGROUND_TRANSACTION: {
- data.enforceInterface(IApplicationThread.descriptor);
- processInBackground();
- return true;
- }
-
- case DUMP_SERVICE_TRANSACTION: {
- data.enforceInterface(IApplicationThread.descriptor);
- ParcelFileDescriptor fd = data.readFileDescriptor();
- final IBinder service = data.readStrongBinder();
- final String[] args = data.readStringArray();
- if (fd != null) {
- dumpService(fd.getFileDescriptor(), service, args);
- try {
- fd.close();
- } catch (IOException e) {
- }
- }
- return true;
- }
-
- case DUMP_PROVIDER_TRANSACTION: {
- data.enforceInterface(IApplicationThread.descriptor);
- ParcelFileDescriptor fd = data.readFileDescriptor();
- final IBinder service = data.readStrongBinder();
- final String[] args = data.readStringArray();
- if (fd != null) {
- dumpProvider(fd.getFileDescriptor(), service, args);
- try {
- fd.close();
- } catch (IOException e) {
- }
- }
- return true;
- }
-
- case SCHEDULE_REGISTERED_RECEIVER_TRANSACTION: {
- data.enforceInterface(IApplicationThread.descriptor);
- IIntentReceiver receiver = IIntentReceiver.Stub.asInterface(
- data.readStrongBinder());
- Intent intent = Intent.CREATOR.createFromParcel(data);
- int resultCode = data.readInt();
- String dataStr = data.readString();
- Bundle extras = data.readBundle();
- boolean ordered = data.readInt() != 0;
- boolean sticky = data.readInt() != 0;
- int sendingUser = data.readInt();
- int processState = data.readInt();
- scheduleRegisteredReceiver(receiver, intent,
- resultCode, dataStr, extras, ordered, sticky, sendingUser, processState);
- return true;
- }
-
- case SCHEDULE_LOW_MEMORY_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- scheduleLowMemory();
- return true;
- }
-
- case SCHEDULE_ACTIVITY_CONFIGURATION_CHANGED_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder b = data.readStrongBinder();
- Configuration overrideConfig = null;
- if (data.readInt() != 0) {
- overrideConfig = Configuration.CREATOR.createFromParcel(data);
- }
- final boolean reportToActivity = data.readInt() == 1;
- scheduleActivityConfigurationChanged(b, overrideConfig, reportToActivity);
- return true;
- }
-
- case SCHEDULE_LOCAL_VOICE_INTERACTION_STARTED_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder token = data.readStrongBinder();
- IVoiceInteractor voiceInteractor = IVoiceInteractor.Stub.asInterface(
- data.readStrongBinder());
- scheduleLocalVoiceInteractionStarted(token, voiceInteractor);
- return true;
- }
-
- case PROFILER_CONTROL_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- boolean start = data.readInt() != 0;
- int profileType = data.readInt();
- ProfilerInfo profilerInfo = data.readInt() != 0
- ? ProfilerInfo.CREATOR.createFromParcel(data) : null;
- profilerControl(start, profilerInfo, profileType);
- return true;
- }
-
- case SET_SCHEDULING_GROUP_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- int group = data.readInt();
- setSchedulingGroup(group);
- return true;
- }
-
- case SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(data);
- CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
- int backupMode = data.readInt();
- scheduleCreateBackupAgent(appInfo, compatInfo, backupMode);
- return true;
- }
-
- case SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(data);
- CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
- scheduleDestroyBackupAgent(appInfo, compatInfo);
- return true;
- }
-
- case DISPATCH_PACKAGE_BROADCAST_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- int cmd = data.readInt();
- String[] packages = data.readStringArray();
- dispatchPackageBroadcast(cmd, packages);
- return true;
- }
-
- case SCHEDULE_CRASH_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- String msg = data.readString();
- scheduleCrash(msg);
- return true;
- }
-
- case DUMP_HEAP_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- boolean managed = data.readInt() != 0;
- String path = data.readString();
- ParcelFileDescriptor fd = data.readInt() != 0
- ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null;
- dumpHeap(managed, path, fd);
- return true;
- }
-
- case DUMP_ACTIVITY_TRANSACTION: {
- data.enforceInterface(IApplicationThread.descriptor);
- ParcelFileDescriptor fd = data.readFileDescriptor();
- final IBinder activity = data.readStrongBinder();
- final String prefix = data.readString();
- final String[] args = data.readStringArray();
- if (fd != null) {
- dumpActivity(fd.getFileDescriptor(), activity, prefix, args);
- try {
- fd.close();
- } catch (IOException e) {
- }
- }
- return true;
- }
-
- case SET_CORE_SETTINGS_TRANSACTION: {
- data.enforceInterface(IApplicationThread.descriptor);
- Bundle settings = data.readBundle();
- setCoreSettings(settings);
- return true;
- }
-
- case UPDATE_PACKAGE_COMPATIBILITY_INFO_TRANSACTION: {
- data.enforceInterface(IApplicationThread.descriptor);
- String pkg = data.readString();
- CompatibilityInfo compat = CompatibilityInfo.CREATOR.createFromParcel(data);
- updatePackageCompatibilityInfo(pkg, compat);
- return true;
- }
-
- case SCHEDULE_TRIM_MEMORY_TRANSACTION: {
- data.enforceInterface(IApplicationThread.descriptor);
- int level = data.readInt();
- scheduleTrimMemory(level);
- return true;
- }
-
- case DUMP_MEM_INFO_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- ParcelFileDescriptor fd = data.readFileDescriptor();
- Debug.MemoryInfo mi = Debug.MemoryInfo.CREATOR.createFromParcel(data);
- boolean checkin = data.readInt() != 0;
- boolean dumpInfo = data.readInt() != 0;
- boolean dumpDalvik = data.readInt() != 0;
- boolean dumpSummaryOnly = data.readInt() != 0;
- boolean dumpUnreachable = data.readInt() != 0;
- String[] args = data.readStringArray();
- if (fd != null) {
- try {
- dumpMemInfo(fd.getFileDescriptor(), mi, checkin, dumpInfo,
- dumpDalvik, dumpSummaryOnly, dumpUnreachable, args);
- } finally {
- try {
- fd.close();
- } catch (IOException e) {
- // swallowed, not propagated back to the caller
- }
- }
- }
- reply.writeNoException();
- return true;
- }
-
- case DUMP_GFX_INFO_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- ParcelFileDescriptor fd = data.readFileDescriptor();
- String[] args = data.readStringArray();
- if (fd != null) {
- try {
- dumpGfxInfo(fd.getFileDescriptor(), args);
- } finally {
- try {
- fd.close();
- } catch (IOException e) {
- // swallowed, not propagated back to the caller
- }
- }
- }
- reply.writeNoException();
- return true;
- }
-
- case DUMP_DB_INFO_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- ParcelFileDescriptor fd = data.readFileDescriptor();
- String[] args = data.readStringArray();
- if (fd != null) {
- try {
- dumpDbInfo(fd.getFileDescriptor(), args);
- } finally {
- try {
- fd.close();
- } catch (IOException e) {
- // swallowed, not propagated back to the caller
- }
- }
- }
- reply.writeNoException();
- return true;
- }
-
- case UNSTABLE_PROVIDER_DIED_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder provider = data.readStrongBinder();
- unstableProviderDied(provider);
- reply.writeNoException();
- return true;
- }
-
- case REQUEST_ASSIST_CONTEXT_EXTRAS_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder activityToken = data.readStrongBinder();
- IBinder requestToken = data.readStrongBinder();
- int requestType = data.readInt();
- int sessionId = data.readInt();
- requestAssistContextExtras(activityToken, requestToken, requestType, sessionId);
- reply.writeNoException();
- return true;
- }
-
- case SCHEDULE_TRANSLUCENT_CONVERSION_COMPLETE_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder token = data.readStrongBinder();
- boolean timeout = data.readInt() == 1;
- scheduleTranslucentConversionComplete(token, timeout);
- reply.writeNoException();
- return true;
- }
-
- case SCHEDULE_ON_NEW_ACTIVITY_OPTIONS_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder token = data.readStrongBinder();
- ActivityOptions options = new ActivityOptions(data.readBundle());
- scheduleOnNewActivityOptions(token, options);
- reply.writeNoException();
- return true;
- }
-
- case SET_PROCESS_STATE_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- int state = data.readInt();
- setProcessState(state);
- reply.writeNoException();
- return true;
- }
-
- case SCHEDULE_INSTALL_PROVIDER_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- ProviderInfo provider = ProviderInfo.CREATOR.createFromParcel(data);
- scheduleInstallProvider(provider);
- reply.writeNoException();
- return true;
- }
-
- case UPDATE_TIME_PREFS_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- byte is24Hour = data.readByte();
- updateTimePrefs(is24Hour == (byte) 1);
- reply.writeNoException();
- return true;
- }
-
- case CANCEL_VISIBLE_BEHIND_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder token = data.readStrongBinder();
- scheduleCancelVisibleBehind(token);
- reply.writeNoException();
- return true;
- }
-
- case BACKGROUND_VISIBLE_BEHIND_CHANGED_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder token = data.readStrongBinder();
- boolean enabled = data.readInt() > 0;
- scheduleBackgroundVisibleBehindChanged(token, enabled);
- reply.writeNoException();
- return true;
- }
-
- case ENTER_ANIMATION_COMPLETE_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- IBinder token = data.readStrongBinder();
- scheduleEnterAnimationComplete(token);
- reply.writeNoException();
- return true;
- }
-
- case NOTIFY_CLEARTEXT_NETWORK_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- final byte[] firstPacket = data.createByteArray();
- notifyCleartextNetwork(firstPacket);
- reply.writeNoException();
- return true;
- }
-
- case START_BINDER_TRACKING_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- startBinderTracking();
- return true;
- }
-
- case STOP_BINDER_TRACKING_AND_DUMP_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- ParcelFileDescriptor fd = data.readFileDescriptor();
- if (fd != null) {
- stopBinderTrackingAndDump(fd.getFileDescriptor());
- try {
- fd.close();
- } catch (IOException e) {
- }
- }
- return true;
- }
-
- case SCHEDULE_MULTI_WINDOW_CHANGED_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- final IBinder b = data.readStrongBinder();
- final boolean inMultiWindow = data.readInt() != 0;
- scheduleMultiWindowModeChanged(b, inMultiWindow);
- return true;
- }
-
- case SCHEDULE_PICTURE_IN_PICTURE_CHANGED_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- final IBinder b = data.readStrongBinder();
- final boolean inPip = data.readInt() != 0;
- schedulePictureInPictureModeChanged(b, inPip);
- return true;
- }
- case HANDLE_TRUST_STORAGE_UPDATE_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- handleTrustStorageUpdate();
- return true;
- }
-
- }
-
- return super.onTransact(code, data, reply, flags);
- }
-
- public IBinder asBinder()
- {
- return this;
- }
-}
-
-class ApplicationThreadProxy implements IApplicationThread {
- private final IBinder mRemote;
-
- public ApplicationThreadProxy(IBinder remote) {
- mRemote = remote;
- }
-
- public final IBinder asBinder() {
- return mRemote;
- }
-
- public final void schedulePauseActivity(IBinder token, boolean finished,
- boolean userLeaving, int configChanges, boolean dontReport) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- data.writeInt(finished ? 1 : 0);
- data.writeInt(userLeaving ? 1 :0);
- data.writeInt(configChanges);
- data.writeInt(dontReport ? 1 : 0);
- mRemote.transact(SCHEDULE_PAUSE_ACTIVITY_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleStopActivity(IBinder token, boolean showWindow,
- int configChanges) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- data.writeInt(showWindow ? 1 : 0);
- data.writeInt(configChanges);
- mRemote.transact(SCHEDULE_STOP_ACTIVITY_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleWindowVisibility(IBinder token,
- boolean showWindow) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- data.writeInt(showWindow ? 1 : 0);
- mRemote.transact(SCHEDULE_WINDOW_VISIBILITY_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleSleeping(IBinder token,
- boolean sleeping) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- data.writeInt(sleeping ? 1 : 0);
- mRemote.transact(SCHEDULE_SLEEPING_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleResumeActivity(IBinder token, int procState, boolean isForward,
- Bundle resumeArgs)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- data.writeInt(procState);
- data.writeInt(isForward ? 1 : 0);
- data.writeBundle(resumeArgs);
- mRemote.transact(SCHEDULE_RESUME_ACTIVITY_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleSendResult(IBinder token, List<ResultInfo> results)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- data.writeTypedList(results);
- mRemote.transact(SCHEDULE_SEND_RESULT_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
- CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
- int procState, Bundle state, PersistableBundle persistentState,
- List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- intent.writeToParcel(data, 0);
- data.writeStrongBinder(token);
- data.writeInt(ident);
- info.writeToParcel(data, 0);
- curConfig.writeToParcel(data, 0);
- if (overrideConfig != null) {
- data.writeInt(1);
- overrideConfig.writeToParcel(data, 0);
- } else {
- data.writeInt(0);
- }
- compatInfo.writeToParcel(data, 0);
- data.writeString(referrer);
- data.writeStrongBinder(voiceInteractor != null ? voiceInteractor.asBinder() : null);
- data.writeInt(procState);
- data.writeBundle(state);
- data.writePersistableBundle(persistentState);
- data.writeTypedList(pendingResults);
- data.writeTypedList(pendingNewIntents);
- data.writeInt(notResumed ? 1 : 0);
- data.writeInt(isForward ? 1 : 0);
- if (profilerInfo != null) {
- data.writeInt(1);
- profilerInfo.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
- } else {
- data.writeInt(0);
- }
- mRemote.transact(SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleRelaunchActivity(IBinder token,
- List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- int configChanges, boolean notResumed, Configuration config,
- Configuration overrideConfig, boolean preserveWindow) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- data.writeTypedList(pendingResults);
- data.writeTypedList(pendingNewIntents);
- data.writeInt(configChanges);
- data.writeInt(notResumed ? 1 : 0);
- config.writeToParcel(data, 0);
- if (overrideConfig != null) {
- data.writeInt(1);
- overrideConfig.writeToParcel(data, 0);
- } else {
- data.writeInt(0);
- }
- data.writeInt(preserveWindow ? 1 : 0);
- mRemote.transact(SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void scheduleNewIntent(List<ReferrerIntent> intents, IBinder token, boolean andPause)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeTypedList(intents);
- data.writeStrongBinder(token);
- data.writeInt(andPause ? 1 : 0);
- mRemote.transact(SCHEDULE_NEW_INTENT_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleDestroyActivity(IBinder token, boolean finishing,
- int configChanges) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- data.writeInt(finishing ? 1 : 0);
- data.writeInt(configChanges);
- mRemote.transact(SCHEDULE_FINISH_ACTIVITY_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleReceiver(Intent intent, ActivityInfo info,
- CompatibilityInfo compatInfo, int resultCode, String resultData,
- Bundle map, boolean sync, int sendingUser, int processState) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- intent.writeToParcel(data, 0);
- info.writeToParcel(data, 0);
- compatInfo.writeToParcel(data, 0);
- data.writeInt(resultCode);
- data.writeString(resultData);
- data.writeBundle(map);
- data.writeInt(sync ? 1 : 0);
- data.writeInt(sendingUser);
- data.writeInt(processState);
- mRemote.transact(SCHEDULE_RECEIVER_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleCreateBackupAgent(ApplicationInfo app,
- CompatibilityInfo compatInfo, int backupMode) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- app.writeToParcel(data, 0);
- compatInfo.writeToParcel(data, 0);
- data.writeInt(backupMode);
- mRemote.transact(SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleDestroyBackupAgent(ApplicationInfo app,
- CompatibilityInfo compatInfo) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- app.writeToParcel(data, 0);
- compatInfo.writeToParcel(data, 0);
- mRemote.transact(SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleCreateService(IBinder token, ServiceInfo info,
- CompatibilityInfo compatInfo, int processState) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- info.writeToParcel(data, 0);
- compatInfo.writeToParcel(data, 0);
- data.writeInt(processState);
- try {
- mRemote.transact(SCHEDULE_CREATE_SERVICE_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- } catch (TransactionTooLargeException e) {
- Log.e("CREATE_SERVICE", "Binder failure starting service; service=" + info);
- throw e;
- }
- data.recycle();
- }
-
- public final void scheduleBindService(IBinder token, Intent intent, boolean rebind,
- int processState) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- intent.writeToParcel(data, 0);
- data.writeInt(rebind ? 1 : 0);
- data.writeInt(processState);
- mRemote.transact(SCHEDULE_BIND_SERVICE_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleUnbindService(IBinder token, Intent intent)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- intent.writeToParcel(data, 0);
- mRemote.transact(SCHEDULE_UNBIND_SERVICE_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
- int flags, Intent args) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- data.writeInt(taskRemoved ? 1 : 0);
- data.writeInt(startId);
- data.writeInt(flags);
- if (args != null) {
- data.writeInt(1);
- args.writeToParcel(data, 0);
- } else {
- data.writeInt(0);
- }
- mRemote.transact(SCHEDULE_SERVICE_ARGS_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleStopService(IBinder token)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- mRemote.transact(SCHEDULE_STOP_SERVICE_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public final void bindApplication(String packageName, ApplicationInfo info,
- List<ProviderInfo> providers, ComponentName testName, ProfilerInfo profilerInfo,
- Bundle testArgs, IInstrumentationWatcher testWatcher,
- IUiAutomationConnection uiAutomationConnection, int debugMode,
- boolean enableBinderTracking, boolean trackAllocation, boolean restrictedBackupMode,
- boolean persistent, Configuration config, CompatibilityInfo compatInfo,
- Map<String, IBinder> services, Bundle coreSettings, String buildSerial)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeString(packageName);
- info.writeToParcel(data, 0);
- data.writeTypedList(providers);
- if (testName == null) {
- data.writeInt(0);
- } else {
- data.writeInt(1);
- testName.writeToParcel(data, 0);
- }
- if (profilerInfo != null) {
- data.writeInt(1);
- profilerInfo.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
- } else {
- data.writeInt(0);
- }
- data.writeBundle(testArgs);
- data.writeStrongInterface(testWatcher);
- data.writeStrongInterface(uiAutomationConnection);
- data.writeInt(debugMode);
- data.writeInt(enableBinderTracking ? 1 : 0);
- data.writeInt(trackAllocation ? 1 : 0);
- data.writeInt(restrictedBackupMode ? 1 : 0);
- data.writeInt(persistent ? 1 : 0);
- config.writeToParcel(data, 0);
- compatInfo.writeToParcel(data, 0);
- data.writeMap(services);
- data.writeBundle(coreSettings);
- data.writeString(buildSerial);
- mRemote.transact(BIND_APPLICATION_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleExit() throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- mRemote.transact(SCHEDULE_EXIT_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleSuicide() throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- mRemote.transact(SCHEDULE_SUICIDE_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleConfigurationChanged(Configuration config)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- config.writeToParcel(data, 0);
- mRemote.transact(SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public final void scheduleLocalVoiceInteractionStarted(IBinder token,
- IVoiceInteractor voiceInteractor) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- data.writeStrongBinder(voiceInteractor != null ? voiceInteractor.asBinder() : null);
- mRemote.transact(SCHEDULE_LOCAL_VOICE_INTERACTION_STARTED_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void updateTimeZone() throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- mRemote.transact(UPDATE_TIME_ZONE_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void clearDnsCache() throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- mRemote.transact(CLEAR_DNS_CACHE_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void setHttpProxy(String proxy, String port, String exclList,
- Uri pacFileUrl) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeString(proxy);
- data.writeString(port);
- data.writeString(exclList);
- pacFileUrl.writeToParcel(data, 0);
- mRemote.transact(SET_HTTP_PROXY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void processInBackground() throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- mRemote.transact(PROCESS_IN_BACKGROUND_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void dumpService(FileDescriptor fd, IBinder token, String[] args)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeFileDescriptor(fd);
- data.writeStrongBinder(token);
- data.writeStringArray(args);
- mRemote.transact(DUMP_SERVICE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void dumpProvider(FileDescriptor fd, IBinder token, String[] args)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeFileDescriptor(fd);
- data.writeStrongBinder(token);
- data.writeStringArray(args);
- mRemote.transact(DUMP_PROVIDER_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
- int resultCode, String dataStr, Bundle extras, boolean ordered,
- boolean sticky, int sendingUser, int processState) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(receiver.asBinder());
- intent.writeToParcel(data, 0);
- data.writeInt(resultCode);
- data.writeString(dataStr);
- data.writeBundle(extras);
- data.writeInt(ordered ? 1 : 0);
- data.writeInt(sticky ? 1 : 0);
- data.writeInt(sendingUser);
- data.writeInt(processState);
- mRemote.transact(SCHEDULE_REGISTERED_RECEIVER_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public final void scheduleLowMemory() throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- mRemote.transact(SCHEDULE_LOW_MEMORY_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public final void scheduleActivityConfigurationChanged(IBinder token,
- Configuration overrideConfig, boolean reportToActivity) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- if (overrideConfig != null) {
- data.writeInt(1);
- overrideConfig.writeToParcel(data, 0);
- } else {
- data.writeInt(0);
- }
- data.writeInt(reportToActivity ? 1 : 0);
- mRemote.transact(SCHEDULE_ACTIVITY_CONFIGURATION_CHANGED_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeInt(start ? 1 : 0);
- data.writeInt(profileType);
- if (profilerInfo != null) {
- data.writeInt(1);
- profilerInfo.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
- } else {
- data.writeInt(0);
- }
- mRemote.transact(PROFILER_CONTROL_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void setSchedulingGroup(int group) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeInt(group);
- mRemote.transact(SET_SCHEDULING_GROUP_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void dispatchPackageBroadcast(int cmd, String[] packages) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeInt(cmd);
- data.writeStringArray(packages);
- mRemote.transact(DISPATCH_PACKAGE_BROADCAST_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void scheduleCrash(String msg) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeString(msg);
- mRemote.transact(SCHEDULE_CRASH_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void dumpHeap(boolean managed, String path,
- ParcelFileDescriptor fd) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeInt(managed ? 1 : 0);
- data.writeString(path);
- if (fd != null) {
- data.writeInt(1);
- fd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
- } else {
- data.writeInt(0);
- }
- mRemote.transact(DUMP_HEAP_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void dumpActivity(FileDescriptor fd, IBinder token, String prefix, String[] args)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeFileDescriptor(fd);
- data.writeStrongBinder(token);
- data.writeString(prefix);
- data.writeStringArray(args);
- mRemote.transact(DUMP_ACTIVITY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void setCoreSettings(Bundle coreSettings) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeBundle(coreSettings);
- mRemote.transact(SET_CORE_SETTINGS_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
- }
-
- public void updatePackageCompatibilityInfo(String pkg, CompatibilityInfo info)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeString(pkg);
- info.writeToParcel(data, 0);
- mRemote.transact(UPDATE_PACKAGE_COMPATIBILITY_INFO_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- }
-
- public void scheduleTrimMemory(int level) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeInt(level);
- mRemote.transact(SCHEDULE_TRIM_MEMORY_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void dumpMemInfo(FileDescriptor fd, Debug.MemoryInfo mem, boolean checkin,
- boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
- boolean dumpUnreachable, String[] args) throws RemoteException {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeFileDescriptor(fd);
- mem.writeToParcel(data, 0);
- data.writeInt(checkin ? 1 : 0);
- data.writeInt(dumpInfo ? 1 : 0);
- data.writeInt(dumpDalvik ? 1 : 0);
- data.writeInt(dumpSummaryOnly ? 1 : 0);
- data.writeInt(dumpUnreachable ? 1 : 0);
- data.writeStringArray(args);
- mRemote.transact(DUMP_MEM_INFO_TRANSACTION, data, reply, 0);
- reply.readException();
- data.recycle();
- reply.recycle();
- }
-
- public void dumpGfxInfo(FileDescriptor fd, String[] args) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeFileDescriptor(fd);
- data.writeStringArray(args);
- mRemote.transact(DUMP_GFX_INFO_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- public void dumpDbInfo(FileDescriptor fd, String[] args) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeFileDescriptor(fd);
- data.writeStringArray(args);
- mRemote.transact(DUMP_DB_INFO_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public void unstableProviderDied(IBinder provider) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(provider);
- mRemote.transact(UNSTABLE_PROVIDER_DIED_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public void requestAssistContextExtras(IBinder activityToken, IBinder requestToken,
- int requestType, int sessionId) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(activityToken);
- data.writeStrongBinder(requestToken);
- data.writeInt(requestType);
- data.writeInt(sessionId);
- mRemote.transact(REQUEST_ASSIST_CONTEXT_EXTRAS_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public void scheduleTranslucentConversionComplete(IBinder token, boolean timeout)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- data.writeInt(timeout ? 1 : 0);
- mRemote.transact(SCHEDULE_TRANSLUCENT_CONVERSION_COMPLETE_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public void scheduleOnNewActivityOptions(IBinder token, ActivityOptions options)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- data.writeBundle(options == null ? null : options.toBundle());
- mRemote.transact(SCHEDULE_ON_NEW_ACTIVITY_OPTIONS_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public void setProcessState(int state) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeInt(state);
- mRemote.transact(SET_PROCESS_STATE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public void scheduleInstallProvider(ProviderInfo provider) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- provider.writeToParcel(data, 0);
- mRemote.transact(SCHEDULE_INSTALL_PROVIDER_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public void updateTimePrefs(boolean is24Hour) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeByte(is24Hour ? (byte) 1 : (byte) 0);
- mRemote.transact(UPDATE_TIME_PREFS_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public void scheduleCancelVisibleBehind(IBinder token) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- mRemote.transact(CANCEL_VISIBLE_BEHIND_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public void scheduleBackgroundVisibleBehindChanged(IBinder token, boolean enabled)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- data.writeInt(enabled ? 1 : 0);
- mRemote.transact(BACKGROUND_VISIBLE_BEHIND_CHANGED_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public void scheduleEnterAnimationComplete(IBinder token) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- mRemote.transact(ENTER_ANIMATION_COMPLETE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public void notifyCleartextNetwork(byte[] firstPacket) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeByteArray(firstPacket);
- mRemote.transact(NOTIFY_CLEARTEXT_NETWORK_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public void startBinderTracking() throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- mRemote.transact(START_BINDER_TRACKING_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public void stopBinderTrackingAndDump(FileDescriptor fd) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeFileDescriptor(fd);
- mRemote.transact(STOP_BINDER_TRACKING_AND_DUMP_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public final void scheduleMultiWindowModeChanged(
- IBinder token, boolean isInMultiWindowMode) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- data.writeInt(isInMultiWindowMode ? 1 : 0);
- mRemote.transact(SCHEDULE_MULTI_WINDOW_CHANGED_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public final void schedulePictureInPictureModeChanged(IBinder token, boolean isInPipMode)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- data.writeStrongBinder(token);
- data.writeInt(isInPipMode ? 1 : 0);
- mRemote.transact(SCHEDULE_PICTURE_IN_PICTURE_CHANGED_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
- @Override
- public void handleTrustStorageUpdate() throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- mRemote.transact(HANDLE_TRUST_STORAGE_UPDATE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-}
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index a4b1a1f..cf794c5 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -16,21 +16,13 @@
package android.app;
-import android.graphics.Rect;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
-import android.transition.Transition;
-import android.transition.TransitionManager;
-import android.transition.TransitionSet;
-import android.util.ArrayMap;
import android.util.Log;
import android.util.LogWriter;
-import android.util.SparseArray;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import com.android.internal.util.FastPrintWriter;
@@ -38,7 +30,6 @@
import java.io.PrintWriter;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
-import java.util.List;
final class BackStackState implements Parcelable {
final int[] mOps;
@@ -52,6 +43,7 @@
final CharSequence mBreadCrumbShortTitleText;
final ArrayList<String> mSharedElementSourceNames;
final ArrayList<String> mSharedElementTargetNames;
+ final boolean mAllowOptimization;
public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) {
final int numOps = bse.mOps.size();
@@ -81,6 +73,7 @@
mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
mSharedElementSourceNames = bse.mSharedElementSourceNames;
mSharedElementTargetNames = bse.mSharedElementTargetNames;
+ mAllowOptimization = bse.mAllowOptimization;
}
public BackStackState(Parcel in) {
@@ -95,6 +88,7 @@
mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mSharedElementSourceNames = in.createStringArrayList();
mSharedElementTargetNames = in.createStringArrayList();
+ mAllowOptimization = in.readInt() != 0;
}
public BackStackRecord instantiate(FragmentManagerImpl fm) {
@@ -137,6 +131,7 @@
bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
bse.mSharedElementSourceNames = mSharedElementSourceNames;
bse.mSharedElementTargetNames = mSharedElementTargetNames;
+ bse.mAllowOptimization = mAllowOptimization;
bse.bumpBackStackNesting(1);
return bse;
}
@@ -157,6 +152,7 @@
TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
dest.writeStringList(mSharedElementSourceNames);
dest.writeStringList(mSharedElementTargetNames);
+ dest.writeInt(mAllowOptimization ? 1 : 0);
}
public static final Parcelable.Creator<BackStackState> CREATOR
@@ -175,7 +171,7 @@
* @hide Entry of an operation on the fragment back stack.
*/
final class BackStackRecord extends FragmentTransaction implements
- FragmentManager.BackStackEntry, Runnable {
+ FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {
static final String TAG = FragmentManagerImpl.TAG;
final FragmentManagerImpl mManager;
@@ -210,6 +206,7 @@
String mName;
boolean mCommitted;
int mIndex = -1;
+ boolean mAllowOptimization;
int mBreadCrumbTitleRes;
CharSequence mBreadCrumbTitleText;
@@ -352,6 +349,7 @@
public BackStackRecord(FragmentManagerImpl manager) {
mManager = manager;
+ mAllowOptimization = Build.isAtLeastO();
}
public int getId() {
@@ -633,6 +631,12 @@
mManager.execSingleAction(this, true);
}
+ @Override
+ public FragmentTransaction setAllowOptimization(boolean allowOptimization) {
+ mAllowOptimization = allowOptimization;
+ return this;
+ }
+
int commitInternal(boolean allowStateLoss) {
if (mCommitted) {
throw new IllegalStateException("commit already called");
@@ -654,94 +658,177 @@
return mIndex;
}
- public void run() {
+ /**
+ * Implementation of {@link android.app.FragmentManagerImpl.OpGenerator}.
+ * This operation is added to the list of pending actions during {@link #commit()}, and
+ * will be executed on the UI thread to run this FragmentTransaction.
+ *
+ * @param records Modified to add this BackStackRecord
+ * @param isRecordPop Modified to add a false (this isn't a pop)
+ * @return true always because the records and isRecordPop will always be changed
+ */
+ @Override
+ public boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) {
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Run: " + this);
}
+ records.add(this);
+ isRecordPop.add(false);
if (mAddToBackStack) {
- if (mIndex < 0) {
- throw new IllegalStateException("addToBackStack() called after commit()");
- }
+ mManager.addBackStackState(this);
}
+ return true;
+ }
- expandReplaceOps();
- bumpBackStackNesting(1);
-
- if (mManager.mCurState >= Fragment.CREATED) {
- SparseArray<FragmentContainerTransition> transitioningFragments = new SparseArray<>();
- calculateFragments(transitioningFragments);
- beginTransition(transitioningFragments);
- }
-
+ boolean interactsWith(int containerId) {
final int numOps = mOps.size();
for (int opNum = 0; opNum < numOps; opNum++) {
final Op op = mOps.get(opNum);
- Fragment f = op.fragment;
+ if (op.fragment.mContainerId == containerId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean interactsWith(ArrayList<BackStackRecord> records, int startIndex, int endIndex) {
+ if (endIndex == startIndex) {
+ return false;
+ }
+ final int numOps = mOps.size();
+ int lastContainer = -1;
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ final Op op = mOps.get(opNum);
+ final int container = op.fragment.mContainerId;
+ if (container != 0 && container != lastContainer) {
+ lastContainer = container;
+ for (int i = startIndex; i < endIndex; i++) {
+ BackStackRecord record = records.get(i);
+ final int numThoseOps = record.mOps.size();
+ for (int thoseOpIndex = 0; thoseOpIndex < numThoseOps; thoseOpIndex++) {
+ final Op thatOp = record.mOps.get(thoseOpIndex);
+ if (thatOp.fragment.mContainerId == container) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Executes the operations contained within this transaction. The Fragment states will only
+ * be modified if optimizations are not allowed.
+ */
+ void executeOps() {
+ final int numOps = mOps.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ final Op op = mOps.get(opNum);
+ final Fragment f = op.fragment;
+ f.setNextTransition(mTransition, mTransitionStyle);
switch (op.cmd) {
case OP_ADD:
- f.mNextAnim = op.enterAnim;
+ f.setNextAnim(op.enterAnim);
mManager.addFragment(f, false);
break;
case OP_REMOVE:
- f.mNextAnim = op.exitAnim;
- mManager.removeFragment(f, mTransition, mTransitionStyle);
+ f.setNextAnim(op.exitAnim);
+ mManager.removeFragment(f);
break;
case OP_HIDE:
- f.mNextAnim = op.exitAnim;
- mManager.hideFragment(f, mTransition, mTransitionStyle);
+ f.setNextAnim(op.exitAnim);
+ mManager.hideFragment(f);
break;
case OP_SHOW:
- f.mNextAnim = op.enterAnim;
- mManager.showFragment(f, mTransition, mTransitionStyle);
+ f.setNextAnim(op.enterAnim);
+ mManager.showFragment(f);
break;
case OP_DETACH:
- f.mNextAnim = op.exitAnim;
- mManager.detachFragment(f, mTransition, mTransitionStyle);
+ f.setNextAnim(op.exitAnim);
+ mManager.detachFragment(f);
break;
case OP_ATTACH:
- f.mNextAnim = op.enterAnim;
- mManager.attachFragment(f, mTransition, mTransitionStyle);
+ f.setNextAnim(op.enterAnim);
+ mManager.attachFragment(f);
break;
default:
throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
}
+ if (!mAllowOptimization && op.cmd != OP_ADD) {
+ mManager.moveFragmentToExpectedState(f);
+ }
}
-
- mManager.moveToState(mManager.mCurState, mTransition,
- mTransitionStyle, true);
-
- if (mAddToBackStack) {
- mManager.addBackStackState(this);
+ if (!mAllowOptimization) {
+ // Added fragments are added at the end to comply with prior behavior.
+ mManager.moveToState(mManager.mCurState);
}
}
- private void expandReplaceOps() {
- final int numOps = mOps.size();
-
- boolean hasReplace = false;
- // Before we do anything, check to see if any replace operations exist:
- for (int opNum = 0; opNum < numOps; opNum++) {
+ /**
+ * Reverses the execution of the operations within this transaction. The Fragment states will
+ * only be modified if optimizations are not allowed.
+ */
+ void executePopOps() {
+ for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
final Op op = mOps.get(opNum);
- if (op.cmd == OP_REPLACE) {
- hasReplace = true;
- break;
+ Fragment f = op.fragment;
+ f.setNextTransition(FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
+ switch (op.cmd) {
+ case OP_ADD:
+ f.setNextAnim(op.popExitAnim);
+ mManager.removeFragment(f);
+ break;
+ case OP_REMOVE:
+ f.setNextAnim(op.popEnterAnim);
+ mManager.addFragment(f, false);
+ break;
+ case OP_HIDE:
+ f.setNextAnim(op.popEnterAnim);
+ mManager.showFragment(f);
+ break;
+ case OP_SHOW:
+ f.setNextAnim(op.popExitAnim);
+ mManager.hideFragment(f);
+ break;
+ case OP_DETACH:
+ f.setNextAnim(op.popEnterAnim);
+ mManager.attachFragment(f);
+ break;
+ case OP_ATTACH:
+ f.setNextAnim(op.popExitAnim);
+ mManager.detachFragment(f);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
+ }
+ if (!mAllowOptimization && op.cmd != OP_ADD) {
+ mManager.moveFragmentToExpectedState(f);
}
}
-
- if (!hasReplace) {
- return; // nothing to expand
+ if (!mAllowOptimization) {
+ mManager.moveToState(mManager.mCurState);
}
+ }
- ArrayList<Fragment> added = (mManager.mAdded == null) ? new ArrayList<Fragment>() :
- new ArrayList<>(mManager.mAdded);
+ /**
+ * Removes all OP_REPLACE ops and replaces them with the proper add and remove
+ * operations that are equivalent to the replace. This must be called prior to
+ * {@link #executeOps()} or any other call that operations on mOps.
+ *
+ * @param added Initialized to the fragments that are in the mManager.mAdded, this
+ * will be modified to contain the fragments that will be in mAdded
+ * after the execution ({@link #executeOps()}.
+ */
+ void expandReplaceOps(ArrayList<Fragment> added) {
for (int opNum = 0; opNum < mOps.size(); opNum++) {
final Op op = mOps.get(opNum);
switch (op.cmd) {
case OP_ADD:
case OP_ATTACH:
added.add(op.fragment);
- break;
+ break;
case OP_REMOVE:
case OP_DETACH:
added.remove(op.fragment);
@@ -782,920 +869,29 @@
}
}
- private static void setFirstOut(SparseArray<FragmentContainerTransition> transitioningFragments,
- Fragment fragment, boolean isPop) {
- if (fragment != null) {
- int containerId = fragment.mContainerId;
- if (containerId != 0 && !fragment.isHidden()) {
- FragmentContainerTransition fragments = transitioningFragments.get(containerId);
- if (fragment.isAdded() && fragment.getView() != null && (fragments == null ||
- fragments.firstOut == null)) {
- if (fragments == null) {
- fragments = new FragmentContainerTransition();
- transitioningFragments.put(containerId, fragments);
- }
- fragments.firstOut = fragment;
- fragments.firstOutIsPop = isPop;
- }
- if (fragments != null && fragments.lastIn == fragment) {
- fragments.lastIn = null;
- }
- }
- }
- }
-
- private void setLastIn(SparseArray<FragmentContainerTransition> transitioningFragments,
- Fragment fragment, boolean isPop) {
- if (fragment != null) {
- int containerId = fragment.mContainerId;
- if (containerId != 0) {
- FragmentContainerTransition fragments = transitioningFragments.get(containerId);
- if (!fragment.isAdded()) {
- if (fragments == null) {
- fragments = new FragmentContainerTransition();
- transitioningFragments.put(containerId, fragments);
- }
- fragments.lastIn = fragment;
- fragments.lastInIsPop = isPop;
- }
- if (fragments != null && fragments.firstOut == fragment) {
- fragments.firstOut = null;
- }
- }
- /**
- * Ensure that fragments that are entering are at least at the CREATED state
- * so that they may load Transitions using TransitionInflater.
- */
- if (fragment.mState < Fragment.CREATED && mManager.mCurState >= Fragment.CREATED &&
- mManager.mHost.getContext().getApplicationInfo().targetSdkVersion >=
- Build.VERSION_CODES.N) {
- mManager.makeActive(fragment);
- mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
- }
- }
- }
-
- /**
- * Finds the first removed fragment and last added fragments when going forward.
- * If none of the fragments have transitions, then both lists will be empty.
- *
- * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
- * and last fragments to be added. This will be modified by
- * this method.
- */
- private void calculateFragments(
- SparseArray<FragmentContainerTransition> transitioningFragments) {
- if (!mManager.mContainer.onHasView()) {
- return; // nothing to see, so no transitions
- }
- final int numOps = mOps.size();
- for (int opNum = 0; opNum < numOps; opNum++) {
+ boolean isPostponed() {
+ for (int opNum = 0; opNum < mOps.size(); opNum++) {
final Op op = mOps.get(opNum);
- switch (op.cmd) {
- case OP_ADD:
- case OP_SHOW:
- case OP_ATTACH:
- setLastIn(transitioningFragments, op.fragment, false);
- break;
- case OP_REMOVE:
- case OP_HIDE:
- case OP_DETACH:
- setFirstOut(transitioningFragments, op.fragment, false);
- break;
- }
- }
- }
-
- /**
- * Finds the first removed fragment and last added fragments when popping the back stack.
- * If none of the fragments have transitions, then both lists will be empty.
- *
- * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
- * and last fragments to be added. This will be modified by
- * this method.
- */
- public void calculateBackFragments(
- SparseArray<FragmentContainerTransition> transitioningFragments) {
- if (!mManager.mContainer.onHasView()) {
- return; // nothing to see, so no transitions
- }
- final int numOps = mOps.size();
- for (int opNum = numOps - 1; opNum >= 0; opNum--) {
- final Op op = mOps.get(opNum);
- switch (op.cmd) {
- case OP_ADD:
- case OP_SHOW:
- case OP_ATTACH:
- setFirstOut(transitioningFragments, op.fragment, true);
- break;
- case OP_REMOVE:
- case OP_HIDE:
- case OP_DETACH:
- setLastIn(transitioningFragments, op.fragment, true);
- break;
- }
- }
- }
-
- /**
- * When custom fragment transitions are used, this sets up the state for each transition
- * and begins the transition. A different transition is started for each fragment container
- * and consists of up to 3 different transitions: the exit transition, a shared element
- * transition and an enter transition.
- *
- * <p>The exit transition operates against the leaf nodes of the first fragment
- * with a view that was removed. If no such fragment was removed, then no exit
- * transition is executed. The exit transition comes from the outgoing fragment.</p>
- *
- * <p>The enter transition operates against the last fragment that was added. If
- * that fragment does not have a view or no fragment was added, then no enter
- * transition is executed. The enter transition comes from the incoming fragment.</p>
- *
- * <p>The shared element transition operates against all views and comes either
- * from the outgoing fragment or the incoming fragment, depending on whether this
- * is going forward or popping the back stack. When going forward, the incoming
- * fragment's enter shared element transition is used, but when going back, the
- * outgoing fragment's return shared element transition is used. Shared element
- * transitions only operate if there is both an incoming and outgoing fragment.</p>
- *
- * @param containers The first in and last out fragments that are transitioning.
- * @return The TransitionState used to complete the operation of the transition
- * in {@link #setNameOverrides(android.app.BackStackRecord.TransitionState, java.util.ArrayList,
- * java.util.ArrayList)}.
- */
- private TransitionState beginTransition(SparseArray<FragmentContainerTransition> containers) {
- TransitionState state = new TransitionState();
-
- // Adding a non-existent target view makes sure that the transitions don't target
- // any views by default. They'll only target the views we tell add. If we don't
- // add any, then no views will be targeted.
- state.nonExistentView = new View(mManager.mHost.getContext());
-
- final int numContainers = containers.size();
- for (int i = 0; i < numContainers; i++) {
- int containerId = containers.keyAt(i);
- FragmentContainerTransition containerTransition = containers.valueAt(i);
- configureTransitions(containerId, state, containerTransition);
- }
- return state;
- }
-
- private static Transition cloneTransition(Transition transition) {
- if (transition != null) {
- transition = transition.clone();
- }
- return transition;
- }
-
- private static Transition getEnterTransition(Fragment inFragment, boolean isBack) {
- if (inFragment == null) {
- return null;
- }
- return cloneTransition(isBack ? inFragment.getReenterTransition() :
- inFragment.getEnterTransition());
- }
-
- private static Transition getExitTransition(Fragment outFragment, boolean isBack) {
- if (outFragment == null) {
- return null;
- }
- return cloneTransition(isBack ? outFragment.getReturnTransition() :
- outFragment.getExitTransition());
- }
-
- private static TransitionSet getSharedElementTransition(Fragment inFragment,
- Fragment outFragment, boolean isBack) {
- if (inFragment == null || outFragment == null) {
- return null;
- }
- Transition transition = cloneTransition(isBack
- ? outFragment.getSharedElementReturnTransition()
- : inFragment.getSharedElementEnterTransition());
- if (transition == null) {
- return null;
- }
- TransitionSet transitionSet = new TransitionSet();
- transitionSet.addTransition(transition);
- return transitionSet;
- }
-
- private static ArrayList<View> captureExitingViews(Transition exitTransition,
- Fragment outFragment, ArrayMap<String, View> namedViews, View nonExistentView) {
- ArrayList<View> viewList = null;
- if (exitTransition != null) {
- viewList = new ArrayList<View>();
- View root = outFragment.getView();
- root.captureTransitioningViews(viewList);
- if (namedViews != null) {
- viewList.removeAll(namedViews.values());
- }
- if (!viewList.isEmpty()) {
- viewList.add(nonExistentView);
- addTargets(exitTransition, viewList);
- }
- }
- return viewList;
- }
-
- private ArrayMap<String, View> remapSharedElements(TransitionState state, Fragment outFragment,
- boolean isBack) {
- ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
- if (mSharedElementSourceNames != null) {
- outFragment.getView().findNamedViews(namedViews);
- if (isBack) {
- namedViews.retainAll(mSharedElementTargetNames);
- } else {
- namedViews = remapNames(mSharedElementSourceNames, mSharedElementTargetNames,
- namedViews);
- }
- }
-
- if (isBack) {
- outFragment.mEnterTransitionCallback.onMapSharedElements(
- mSharedElementTargetNames, namedViews);
- setBackNameOverrides(state, namedViews, false);
- } else {
- outFragment.mExitTransitionCallback.onMapSharedElements(
- mSharedElementTargetNames, namedViews);
- setNameOverrides(state, namedViews, false);
- }
-
- return namedViews;
- }
-
- /**
- * Prepares the enter transition by adding a non-existent view to the transition's target list
- * and setting it epicenter callback. By adding a non-existent view to the target list,
- * we can prevent any view from being targeted at the beginning of the transition.
- * We will add to the views before the end state of the transition is captured so that the
- * views will appear. At the start of the transition, we clear the list of targets so that
- * we can restore the state of the transition and use it again.
- *
- * <p>The shared element transition maps its shared elements immediately prior to
- * capturing the final state of the Transition.</p>
- */
- private ArrayList<View> addTransitionTargets(final TransitionState state,
- final Transition enterTransition, final TransitionSet sharedElementTransition,
- final Transition exitTransition, final Transition overallTransition,
- final View container, final Fragment inFragment, final Fragment outFragment,
- final ArrayList<View> hiddenFragmentViews, final boolean isBack,
- final ArrayList<View> sharedElementTargets) {
- if (enterTransition == null && sharedElementTransition == null &&
- overallTransition == null) {
- return null;
- }
- final ArrayList<View> enteringViews = new ArrayList<View>();
- container.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- container.getViewTreeObserver().removeOnPreDrawListener(this);
-
- // Don't include any newly-hidden fragments in the transition.
- if (inFragment != null) {
- excludeHiddenFragments(hiddenFragmentViews, inFragment.mContainerId,
- overallTransition);
- }
-
- ArrayMap<String, View> namedViews = null;
- if (sharedElementTransition != null) {
- namedViews = mapSharedElementsIn(state, isBack, inFragment);
- removeTargets(sharedElementTransition, sharedElementTargets);
- // keep the nonExistentView as excluded so the list doesn't get emptied
- sharedElementTargets.remove(state.nonExistentView);
- excludeViews(exitTransition, sharedElementTransition,
- sharedElementTargets, false);
- excludeViews(enterTransition, sharedElementTransition,
- sharedElementTargets, false);
-
- setSharedElementTargets(sharedElementTransition,
- state.nonExistentView, namedViews, sharedElementTargets);
-
- setEpicenterIn(namedViews, state);
-
- callSharedElementEnd(state, inFragment, outFragment, isBack,
- namedViews);
- }
-
- if (enterTransition != null) {
- enterTransition.removeTarget(state.nonExistentView);
- View view = inFragment.getView();
- if (view != null) {
- view.captureTransitioningViews(enteringViews);
- if (namedViews != null) {
- enteringViews.removeAll(namedViews.values());
- }
- enteringViews.add(state.nonExistentView);
- // We added this earlier to prevent any views being targeted.
- addTargets(enterTransition, enteringViews);
- }
- setSharedElementEpicenter(enterTransition, state);
- }
-
- excludeViews(exitTransition, enterTransition, enteringViews, true);
- excludeViews(exitTransition, sharedElementTransition, sharedElementTargets,
- true);
- excludeViews(enterTransition, sharedElementTransition, sharedElementTargets,
- true);
- return true;
- }
- });
- return enteringViews;
- }
-
- private void callSharedElementEnd(TransitionState state, Fragment inFragment,
- Fragment outFragment, boolean isBack, ArrayMap<String, View> namedViews) {
- SharedElementCallback sharedElementCallback = isBack ?
- outFragment.mEnterTransitionCallback :
- inFragment.mEnterTransitionCallback;
- ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
- ArrayList<View> views = new ArrayList<View>(namedViews.values());
- sharedElementCallback.onSharedElementEnd(names, views, null);
- }
-
- private void setEpicenterIn(ArrayMap<String, View> namedViews, TransitionState state) {
- if (mSharedElementTargetNames != null && !namedViews.isEmpty()) {
- // now we know the epicenter of the entering transition.
- View epicenter = namedViews
- .get(mSharedElementTargetNames.get(0));
- if (epicenter != null) {
- state.enteringEpicenterView = epicenter;
- }
- }
- }
-
- private ArrayMap<String, View> mapSharedElementsIn(TransitionState state,
- boolean isBack, Fragment inFragment) {
- // Now map the shared elements in the incoming fragment
- ArrayMap<String, View> namedViews = mapEnteringSharedElements(state, inFragment, isBack);
-
- // remap shared elements and set the name mapping used
- // in the shared element transition.
- if (isBack) {
- inFragment.mExitTransitionCallback.onMapSharedElements(
- mSharedElementTargetNames, namedViews);
- setBackNameOverrides(state, namedViews, true);
- } else {
- inFragment.mEnterTransitionCallback.onMapSharedElements(
- mSharedElementTargetNames, namedViews);
- setNameOverrides(state, namedViews, true);
- }
- return namedViews;
- }
-
- private static Transition mergeTransitions(Transition enterTransition,
- Transition exitTransition, Transition sharedElementTransition, Fragment inFragment,
- boolean isBack) {
- boolean overlap = true;
- if (enterTransition != null && exitTransition != null && inFragment != null) {
- overlap = isBack ? inFragment.getAllowReturnTransitionOverlap() :
- inFragment.getAllowEnterTransitionOverlap();
- }
-
- // Wrap the transitions. Explicit targets like in enter and exit will cause the
- // views to be targeted regardless of excluded views. If that happens, then the
- // excluded fragments views (hidden fragments) will still be in the transition.
-
- Transition transition;
- if (overlap) {
- // Regular transition -- do it all together
- TransitionSet transitionSet = new TransitionSet();
- if (enterTransition != null) {
- transitionSet.addTransition(enterTransition);
- }
- if (exitTransition != null) {
- transitionSet.addTransition(exitTransition);
- }
- if (sharedElementTransition != null) {
- transitionSet.addTransition(sharedElementTransition);
- }
- transition = transitionSet;
- } else {
- // First do exit, then enter, but allow shared element transition to happen
- // during both.
- Transition staggered = null;
- if (exitTransition != null && enterTransition != null) {
- staggered = new TransitionSet()
- .addTransition(exitTransition)
- .addTransition(enterTransition)
- .setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
- } else if (exitTransition != null) {
- staggered = exitTransition;
- } else if (enterTransition != null) {
- staggered = enterTransition;
- }
- if (sharedElementTransition != null) {
- TransitionSet together = new TransitionSet();
- if (staggered != null) {
- together.addTransition(staggered);
- }
- together.addTransition(sharedElementTransition);
- transition = together;
- } else {
- transition = staggered;
- }
- }
- return transition;
- }
-
- /**
- * Configures custom transitions for a specific fragment container.
- *
- * @param containerId The container ID of the fragments to configure the transition for.
- * @param state The Transition State keeping track of the executing transitions.
- * @param transitioningFragments The first out and last in fragments for the fragment container.
- */
- private void configureTransitions(int containerId, TransitionState state,
- FragmentContainerTransition transitioningFragments) {
- ViewGroup sceneRoot = (ViewGroup) mManager.mContainer.onFindViewById(containerId);
- if (sceneRoot != null) {
- final Fragment inFragment = transitioningFragments.lastIn;
- final Fragment outFragment = transitioningFragments.firstOut;
-
- Transition enterTransition =
- getEnterTransition(inFragment, transitioningFragments.lastInIsPop);
- TransitionSet sharedElementTransition = getSharedElementTransition(inFragment,
- outFragment, transitioningFragments.lastInIsPop);
- Transition exitTransition =
- getExitTransition(outFragment, transitioningFragments.firstOutIsPop);
-
- if (enterTransition == null && sharedElementTransition == null &&
- exitTransition == null) {
- return; // no transitions!
- }
- if (enterTransition != null) {
- enterTransition.addTarget(state.nonExistentView);
- }
- ArrayMap<String, View> namedViews = null;
- ArrayList<View> sharedElementTargets = new ArrayList<View>();
- if (sharedElementTransition != null) {
- namedViews = remapSharedElements(state, outFragment,
- transitioningFragments.firstOutIsPop);
- setSharedElementTargets(sharedElementTransition,
- state.nonExistentView, namedViews, sharedElementTargets);
-
- // Notify the start of the transition.
- SharedElementCallback callback = transitioningFragments.lastInIsPop ?
- outFragment.mEnterTransitionCallback :
- inFragment.mEnterTransitionCallback;
- ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
- ArrayList<View> views = new ArrayList<View>(namedViews.values());
- callback.onSharedElementStart(names, views, null);
- }
-
- ArrayList<View> exitingViews = captureExitingViews(exitTransition, outFragment,
- namedViews, state.nonExistentView);
- if (exitingViews == null || exitingViews.isEmpty()) {
- exitTransition = null;
- }
- excludeViews(enterTransition, exitTransition, exitingViews, true);
- excludeViews(enterTransition, sharedElementTransition, sharedElementTargets, true);
- excludeViews(exitTransition, sharedElementTransition, sharedElementTargets, true);
-
- // Set the epicenter of the exit transition
- if (mSharedElementTargetNames != null && namedViews != null) {
- View epicenterView = namedViews.get(mSharedElementTargetNames.get(0));
- if (epicenterView != null) {
- if (exitTransition != null) {
- setEpicenter(exitTransition, epicenterView);
- }
- if (sharedElementTransition != null) {
- setEpicenter(sharedElementTransition, epicenterView);
- }
- }
- }
-
- Transition transition = mergeTransitions(enterTransition, exitTransition,
- sharedElementTransition, inFragment, transitioningFragments.lastInIsPop);
-
- if (transition != null) {
- ArrayList<View> hiddenFragments = new ArrayList<View>();
- ArrayList<View> enteringViews = addTransitionTargets(state, enterTransition,
- sharedElementTransition, exitTransition, transition, sceneRoot, inFragment,
- outFragment, hiddenFragments, transitioningFragments.lastInIsPop,
- sharedElementTargets);
-
- transition.setNameOverrides(state.nameOverrides);
- // We want to exclude hidden views later, so we need a non-null list in the
- // transition now.
- transition.excludeTarget(state.nonExistentView, true);
- // Now exclude all currently hidden fragments.
- excludeHiddenFragments(hiddenFragments, containerId, transition);
- TransitionManager.beginDelayedTransition(sceneRoot, transition);
- // Remove the view targeting after the transition starts
- removeTargetedViewsFromTransitions(sceneRoot, state.nonExistentView,
- enterTransition, enteringViews, exitTransition, exitingViews,
- sharedElementTransition, sharedElementTargets, transition,
- hiddenFragments);
- }
- }
- }
-
- /**
- * Finds all children of the shared elements and sets the wrapping TransitionSet
- * targets to point to those. It also limits transitions that have no targets to the
- * specific shared elements. This allows developers to target child views of the
- * shared elements specifically, but this doesn't happen by default.
- */
- private static void setSharedElementTargets(TransitionSet transition,
- View nonExistentView, ArrayMap<String, View> namedViews,
- ArrayList<View> sharedElementTargets) {
- sharedElementTargets.clear();
- sharedElementTargets.addAll(namedViews.values());
-
- final List<View> views = transition.getTargets();
- views.clear();
- final int count = sharedElementTargets.size();
- for (int i = 0; i < count; i++) {
- final View view = sharedElementTargets.get(i);
- bfsAddViewChildren(views, view);
- }
- sharedElementTargets.add(nonExistentView);
- addTargets(transition, sharedElementTargets);
- }
-
- /**
- * Uses a breadth-first scheme to add startView and all of its children to views.
- * It won't add a child if it is already in views.
- */
- private static void bfsAddViewChildren(final List<View> views, final View startView) {
- final int startIndex = views.size();
- if (containedBeforeIndex(views, startView, startIndex)) {
- return; // This child is already in the list, so all its children are also.
- }
- views.add(startView);
- for (int index = startIndex; index < views.size(); index++) {
- final View view = views.get(index);
- if (view instanceof ViewGroup) {
- ViewGroup viewGroup = (ViewGroup) view;
- final int childCount = viewGroup.getChildCount();
- for (int childIndex = 0; childIndex < childCount; childIndex++) {
- final View child = viewGroup.getChildAt(childIndex);
- if (!containedBeforeIndex(views, child, startIndex)) {
- views.add(child);
- }
- }
- }
- }
- }
-
- /**
- * Does a linear search through views for view, limited to maxIndex.
- */
- private static boolean containedBeforeIndex(final List<View> views, final View view,
- final int maxIndex) {
- for (int i = 0; i < maxIndex; i++) {
- if (views.get(i) == view) {
+ if (isFragmentPostponed(op)) {
return true;
}
}
return false;
}
- private static void excludeViews(Transition transition, Transition fromTransition,
- ArrayList<View> views, boolean exclude) {
- if (transition != null) {
- final int viewCount = fromTransition == null ? 0 : views.size();
- for (int i = 0; i < viewCount; i++) {
- transition.excludeTarget(views.get(i), exclude);
- }
- }
- }
-
- /**
- * After the transition has started, remove all targets that we added to the transitions
- * so that the transitions are left in a clean state.
- */
- private void removeTargetedViewsFromTransitions(
- final ViewGroup sceneRoot, final View nonExistingView,
- final Transition enterTransition, final ArrayList<View> enteringViews,
- final Transition exitTransition, final ArrayList<View> exitingViews,
- final Transition sharedElementTransition, final ArrayList<View> sharedElementTargets,
- final Transition overallTransition, final ArrayList<View> hiddenViews) {
- if (overallTransition != null) {
- sceneRoot.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
- if (enterTransition != null) {
- removeTargets(enterTransition, enteringViews);
- excludeViews(enterTransition, exitTransition, exitingViews, false);
- excludeViews(enterTransition, sharedElementTransition, sharedElementTargets,
- false);
- }
- if (exitTransition != null) {
- removeTargets(exitTransition, exitingViews);
- excludeViews(exitTransition, enterTransition, enteringViews, false);
- excludeViews(exitTransition, sharedElementTransition, sharedElementTargets,
- false);
- }
- if (sharedElementTransition != null) {
- removeTargets(sharedElementTransition, sharedElementTargets);
- }
- int numViews = hiddenViews.size();
- for (int i = 0; i < numViews; i++) {
- overallTransition.excludeTarget(hiddenViews.get(i), false);
- }
- overallTransition.excludeTarget(nonExistingView, false);
- return true;
- }
- });
- }
- }
-
- /**
- * This method removes the views from transitions that target ONLY those views.
- * The views list should match those added in addTargets and should contain
- * one view that is not in the view hierarchy (state.nonExistentView).
- */
- public static void removeTargets(Transition transition, ArrayList<View> views) {
- if (transition instanceof TransitionSet) {
- TransitionSet set = (TransitionSet) transition;
- int numTransitions = set.getTransitionCount();
- for (int i = 0; i < numTransitions; i++) {
- Transition child = set.getTransitionAt(i);
- removeTargets(child, views);
- }
- } else if (!hasSimpleTarget(transition)) {
- List<View> targets = transition.getTargets();
- if (targets != null && targets.size() == views.size() &&
- targets.containsAll(views)) {
- // We have an exact match. We must have added these earlier in addTargets
- for (int i = views.size() - 1; i >= 0; i--) {
- transition.removeTarget(views.get(i));
- }
- }
- }
- }
-
- /**
- * This method adds views as targets to the transition, but only if the transition
- * doesn't already have a target. It is best for views to contain one View object
- * that does not exist in the view hierarchy (state.nonExistentView) so that
- * when they are removed later, a list match will suffice to remove the targets.
- * Otherwise, if you happened to have targeted the exact views for the transition,
- * the removeTargets call will remove them unexpectedly.
- */
- public static void addTargets(Transition transition, ArrayList<View> views) {
- if (transition instanceof TransitionSet) {
- TransitionSet set = (TransitionSet) transition;
- int numTransitions = set.getTransitionCount();
- for (int i = 0; i < numTransitions; i++) {
- Transition child = set.getTransitionAt(i);
- addTargets(child, views);
- }
- } else if (!hasSimpleTarget(transition)) {
- List<View> targets = transition.getTargets();
- if (isNullOrEmpty(targets)) {
- // We can just add the target views
- int numViews = views.size();
- for (int i = 0; i < numViews; i++) {
- transition.addTarget(views.get(i));
- }
- }
- }
- }
-
- private static boolean hasSimpleTarget(Transition transition) {
- return !isNullOrEmpty(transition.getTargetIds()) ||
- !isNullOrEmpty(transition.getTargetNames()) ||
- !isNullOrEmpty(transition.getTargetTypes());
- }
-
- private static boolean isNullOrEmpty(List list) {
- return list == null || list.isEmpty();
- }
-
- /**
- * Remaps a name-to-View map, substituting different names for keys.
- *
- * @param inMap A list of keys found in the map, in the order in toGoInMap
- * @param toGoInMap A list of keys to use for the new map, in the order of inMap
- * @param namedViews The current mapping
- * @return a new Map after it has been mapped with the new names as keys.
- */
- private static ArrayMap<String, View> remapNames(ArrayList<String> inMap,
- ArrayList<String> toGoInMap, ArrayMap<String, View> namedViews) {
- ArrayMap<String, View> remappedViews = new ArrayMap<String, View>();
- if (!namedViews.isEmpty()) {
- int numKeys = inMap.size();
- for (int i = 0; i < numKeys; i++) {
- View view = namedViews.get(inMap.get(i));
-
- if (view != null) {
- remappedViews.put(toGoInMap.get(i), view);
- }
- }
- }
- return remappedViews;
- }
-
- /**
- * Maps shared elements to views in the entering fragment.
- *
- * @param state The transition State as returned from {@link #beginTransition(
- * android.util.SparseArray, android.util.SparseArray, boolean)}.
- * @param inFragment The last fragment to be added.
- * @param isBack true if this is popping the back stack or false if this is a
- * forward operation.
- */
- private ArrayMap<String, View> mapEnteringSharedElements(TransitionState state,
- Fragment inFragment, boolean isBack) {
- ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
- View root = inFragment.getView();
- if (root != null) {
- if (mSharedElementSourceNames != null) {
- root.findNamedViews(namedViews);
- if (isBack) {
- namedViews = remapNames(mSharedElementSourceNames,
- mSharedElementTargetNames, namedViews);
- } else {
- namedViews.retainAll(mSharedElementTargetNames);
- }
- }
- }
- return namedViews;
- }
-
- private void excludeHiddenFragments(final ArrayList<View> hiddenFragmentViews, int containerId,
- Transition transition) {
- if (mManager.mAdded != null) {
- for (int i = 0; i < mManager.mAdded.size(); i++) {
- Fragment fragment = mManager.mAdded.get(i);
- if (fragment.mView != null && fragment.mContainer != null &&
- fragment.mContainerId == containerId) {
- if (fragment.mHidden) {
- if (!hiddenFragmentViews.contains(fragment.mView)) {
- transition.excludeTarget(fragment.mView, true);
- hiddenFragmentViews.add(fragment.mView);
- }
- } else {
- transition.excludeTarget(fragment.mView, false);
- hiddenFragmentViews.remove(fragment.mView);
- }
- }
- }
- }
- }
-
- private static void setEpicenter(Transition transition, View view) {
- final Rect epicenter = new Rect();
- view.getBoundsOnScreen(epicenter);
-
- transition.setEpicenterCallback(new Transition.EpicenterCallback() {
- @Override
- public Rect onGetEpicenter(Transition transition) {
- return epicenter;
- }
- });
- }
-
- private void setSharedElementEpicenter(Transition transition, final TransitionState state) {
- transition.setEpicenterCallback(new Transition.EpicenterCallback() {
- private Rect mEpicenter;
-
- @Override
- public Rect onGetEpicenter(Transition transition) {
- if (mEpicenter == null && state.enteringEpicenterView != null) {
- mEpicenter = new Rect();
- state.enteringEpicenterView.getBoundsOnScreen(mEpicenter);
- }
- return mEpicenter;
- }
- });
- }
-
- public TransitionState popFromBackStack(boolean doStateMove, TransitionState state,
- SparseArray<FragmentContainerTransition> transitioningFragments) {
- if (FragmentManagerImpl.DEBUG) {
- Log.v(TAG, "popFromBackStack: " + this);
- LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
- PrintWriter pw = new FastPrintWriter(logw, false, 1024);
- dump(" ", null, pw, null);
- pw.flush();
- }
-
- if (mManager.mCurState >= Fragment.CREATED) {
- if (state == null) {
- if (transitioningFragments.size() != 0) {
- state = beginTransition(transitioningFragments);
- }
- } else if (!doStateMove) {
- setNameOverrides(state, mSharedElementTargetNames, mSharedElementSourceNames);
- }
- }
-
- bumpBackStackNesting(-1);
-
- final int numOps = mOps.size();
- for (int opNum = numOps - 1; opNum >= 0; opNum--) {
+ void setOnStartPostponedListener(Fragment.OnStartEnterTransitionListener listener) {
+ for (int opNum = 0; opNum < mOps.size(); opNum++) {
final Op op = mOps.get(opNum);
- Fragment f = op.fragment;
- switch (op.cmd) {
- case OP_ADD:
- f.mNextAnim = op.popExitAnim;
- mManager.removeFragment(f,
- FragmentManagerImpl.reverseTransit(mTransition),
- mTransitionStyle);
- break;
- case OP_REMOVE:
- f.mNextAnim = op.popEnterAnim;
- mManager.addFragment(f, false);
- break;
- case OP_HIDE:
- f.mNextAnim = op.popEnterAnim;
- mManager.showFragment(f,
- FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
- break;
- case OP_SHOW:
- f.mNextAnim = op.popExitAnim;
- mManager.hideFragment(f,
- FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
- break;
- case OP_DETACH:
- f.mNextAnim = op.popEnterAnim;
- mManager.attachFragment(f,
- FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
- break;
- case OP_ATTACH:
- f.mNextAnim = op.popExitAnim;
- mManager.detachFragment(f,
- FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
- break;
- default:
- throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
- }
- }
-
- if (doStateMove) {
- mManager.moveToState(mManager.mCurState,
- FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true);
- state = null;
- }
-
- if (mIndex >= 0) {
- mManager.freeBackStackIndex(mIndex);
- mIndex = -1;
- }
- return state;
- }
-
- private static void setNameOverride(ArrayMap<String, String> overrides,
- String source, String target) {
- if (source != null && target != null && !source.equals(target)) {
- for (int index = 0; index < overrides.size(); index++) {
- if (source.equals(overrides.valueAt(index))) {
- overrides.setValueAt(index, target);
- return;
- }
- }
- overrides.put(source, target);
- }
- }
-
- private static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames,
- ArrayList<String> targetNames) {
- if (sourceNames != null && targetNames != null) {
- for (int i = 0; i < sourceNames.size(); i++) {
- String source = sourceNames.get(i);
- String target = targetNames.get(i);
- setNameOverride(state.nameOverrides, source, target);
+ if (isFragmentPostponed(op)) {
+ op.fragment.setOnStartEnterTransitionListener(listener);
}
}
}
- private void setBackNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
- boolean isEnd) {
- int targetCount = mSharedElementTargetNames == null ? 0 : mSharedElementTargetNames.size();
- int sourceCount = mSharedElementSourceNames == null ? 0 : mSharedElementSourceNames.size();
- final int count = Math.min(targetCount, sourceCount);
- for (int i = 0; i < count; i++) {
- String source = mSharedElementSourceNames.get(i);
- String originalTarget = mSharedElementTargetNames.get(i);
- View view = namedViews.get(originalTarget);
- if (view != null) {
- String target = view.getTransitionName();
- if (isEnd) {
- setNameOverride(state.nameOverrides, source, target);
- } else {
- setNameOverride(state.nameOverrides, target, source);
- }
- }
- }
- }
-
- private void setNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
- boolean isEnd) {
- int count = namedViews == null ? 0 : namedViews.size();
- for (int i = 0; i < count; i++) {
- String source = namedViews.keyAt(i);
- String target = namedViews.valueAt(i).getTransitionName();
- if (isEnd) {
- setNameOverride(state.nameOverrides, source, target);
- } else {
- setNameOverride(state.nameOverrides, target, source);
- }
- }
+ private static boolean isFragmentPostponed(Op op) {
+ final Fragment fragment = op.fragment;
+ return (fragment.mAdded && fragment.mView != null && !fragment.mDetached &&
+ !fragment.mHidden && fragment.isPostponed());
}
public String getName() {
@@ -1713,36 +909,4 @@
public boolean isEmpty() {
return mOps.isEmpty();
}
-
- public class TransitionState {
- public ArrayMap<String, String> nameOverrides = new ArrayMap<String, String>();
- public View enteringEpicenterView;
- public View nonExistentView;
- }
-
- /**
- * Tracks the last fragment added and first fragment removed for fragment transitions.
- * This also tracks which fragments are changed by push or pop transactions.
- */
- public static class FragmentContainerTransition {
- /**
- * The last fragment added/attached/shown in its container
- */
- public Fragment lastIn;
-
- /**
- * true when lastIn was added during a pop transaction or false if added with a push
- */
- public boolean lastInIsPop;
-
- /**
- * The first fragment with a View that was removed/detached/hidden in its container.
- */
- public Fragment firstOut;
-
- /**
- * true when firstOut was removed during a pop transaction or false otherwise
- */
- public boolean firstOutIsPop;
- }
}
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 6ea170e..5d1cd3b 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -31,6 +31,8 @@
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.transition.Transition;
@@ -375,15 +377,6 @@
int mState = INITIALIZING;
- // Non-null if the fragment's view hierarchy is currently animating away,
- // meaning we need to wait a bit on completely destroying it. This is the
- // animation that is running.
- Animator mAnimatingAway;
-
- // If mAnimatingAway != null, this is the state we should move to once the
- // animation is done.
- int mStateAfterAnimating;
-
// When instantiated from saved state, this is the saved state.
Bundle mSavedFragmentState;
SparseArray<Parcelable> mSavedViewState;
@@ -478,9 +471,6 @@
// Used to verify that subclasses call through to super class.
boolean mCalled;
- // If app has requested a specific animation, this is the one to use.
- int mNextAnim;
-
// The parent container of the fragment after dynamically added to UI.
ViewGroup mContainer;
@@ -498,17 +488,18 @@
boolean mLoadersStarted;
boolean mCheckedForLoaderManager;
- private Transition mEnterTransition = null;
- private Transition mReturnTransition = USE_DEFAULT_TRANSITION;
- private Transition mExitTransition = null;
- private Transition mReenterTransition = USE_DEFAULT_TRANSITION;
- private Transition mSharedElementEnterTransition = null;
- private Transition mSharedElementReturnTransition = USE_DEFAULT_TRANSITION;
- private Boolean mAllowReturnTransitionOverlap;
- private Boolean mAllowEnterTransitionOverlap;
+ // The animation and transition information for the fragment. This will be null
+ // unless the elements are explicitly accessed and should remain null for Fragments
+ // without Views.
+ AnimationInfo mAnimationInfo;
- SharedElementCallback mEnterTransitionCallback = SharedElementCallback.NULL_CALLBACK;
- SharedElementCallback mExitTransitionCallback = SharedElementCallback.NULL_CALLBACK;
+ // True if the View was added, and its animation has yet to be run. This could
+ // also indicate that the fragment view hasn't been made visible, even if there is no
+ // animation for this fragment.
+ boolean mIsNewlyAdded;
+
+ // True if mHidden has been changed and the animation should be scheduled.
+ boolean mHiddenChanged;
/**
* State information that has been retrieved from a fragment instance
@@ -1390,26 +1381,41 @@
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.Fragment);
- mEnterTransition = loadTransition(context, a, mEnterTransition, null,
- com.android.internal.R.styleable.Fragment_fragmentEnterTransition);
- mReturnTransition = loadTransition(context, a, mReturnTransition, USE_DEFAULT_TRANSITION,
- com.android.internal.R.styleable.Fragment_fragmentReturnTransition);
- mExitTransition = loadTransition(context, a, mExitTransition, null,
- com.android.internal.R.styleable.Fragment_fragmentExitTransition);
- mReenterTransition = loadTransition(context, a, mReenterTransition, USE_DEFAULT_TRANSITION,
- com.android.internal.R.styleable.Fragment_fragmentReenterTransition);
- mSharedElementEnterTransition = loadTransition(context, a, mSharedElementEnterTransition,
- null, com.android.internal.R.styleable.Fragment_fragmentSharedElementEnterTransition);
- mSharedElementReturnTransition = loadTransition(context, a, mSharedElementReturnTransition,
+ setEnterTransition(loadTransition(context, a, getEnterTransition(), null,
+ com.android.internal.R.styleable.Fragment_fragmentEnterTransition));
+ setReturnTransition(loadTransition(context, a, getReturnTransition(),
USE_DEFAULT_TRANSITION,
- com.android.internal.R.styleable.Fragment_fragmentSharedElementReturnTransition);
- if (mAllowEnterTransitionOverlap == null) {
- mAllowEnterTransitionOverlap = a.getBoolean(
- com.android.internal.R.styleable.Fragment_fragmentAllowEnterTransitionOverlap, true);
+ com.android.internal.R.styleable.Fragment_fragmentReturnTransition));
+ setExitTransition(loadTransition(context, a, getExitTransition(), null,
+ com.android.internal.R.styleable.Fragment_fragmentExitTransition));
+
+ setReenterTransition(loadTransition(context, a, getReenterTransition(),
+ USE_DEFAULT_TRANSITION,
+ com.android.internal.R.styleable.Fragment_fragmentReenterTransition));
+ setSharedElementEnterTransition(loadTransition(context, a,
+ getSharedElementEnterTransition(), null,
+ com.android.internal.R.styleable.Fragment_fragmentSharedElementEnterTransition));
+ setSharedElementReturnTransition(loadTransition(context, a,
+ getSharedElementReturnTransition(), USE_DEFAULT_TRANSITION,
+ com.android.internal.R.styleable.Fragment_fragmentSharedElementReturnTransition));
+ boolean isEnterSet;
+ boolean isReturnSet;
+ if (mAnimationInfo == null) {
+ isEnterSet = false;
+ isReturnSet = false;
+ } else {
+ isEnterSet = mAnimationInfo.mAllowEnterTransitionOverlap != null;
+ isReturnSet = mAnimationInfo.mAllowReturnTransitionOverlap != null;
}
- if (mAllowReturnTransitionOverlap == null) {
- mAllowReturnTransitionOverlap = a.getBoolean(
- com.android.internal.R.styleable.Fragment_fragmentAllowReturnTransitionOverlap, true);
+ if (!isEnterSet) {
+ setAllowEnterTransitionOverlap(a.getBoolean(
+ com.android.internal.R.styleable.Fragment_fragmentAllowEnterTransitionOverlap,
+ true));
+ }
+ if (!isReturnSet) {
+ setAllowReturnTransitionOverlap(a.getBoolean(
+ com.android.internal.R.styleable.Fragment_fragmentAllowReturnTransitionOverlap,
+ true));
}
a.recycle();
@@ -1934,16 +1940,12 @@
*/
public void setEnterSharedElementCallback(SharedElementCallback callback) {
if (callback == null) {
+ if (mAnimationInfo == null) {
+ return; // already a null callback
+ }
callback = SharedElementCallback.NULL_CALLBACK;
}
- mEnterTransitionCallback = callback;
- }
-
- /**
- * @hide
- */
- public void setEnterSharedElementTransitionCallback(SharedElementCallback callback) {
- setEnterSharedElementCallback(callback);
+ ensureAnimationInfo().mEnterTransitionCallback = callback;
}
/**
@@ -1955,16 +1957,12 @@
*/
public void setExitSharedElementCallback(SharedElementCallback callback) {
if (callback == null) {
+ if (mAnimationInfo == null) {
+ return; // already a null callback
+ }
callback = SharedElementCallback.NULL_CALLBACK;
}
- mExitTransitionCallback = callback;
- }
-
- /**
- * @hide
- */
- public void setExitSharedElementTransitionCallback(SharedElementCallback callback) {
- setExitSharedElementCallback(callback);
+ ensureAnimationInfo().mExitTransitionCallback = callback;
}
/**
@@ -1979,7 +1977,9 @@
* @attr ref android.R.styleable#Fragment_fragmentEnterTransition
*/
public void setEnterTransition(Transition transition) {
- mEnterTransition = transition;
+ if (shouldChangeTransition(transition, null)) {
+ ensureAnimationInfo().mEnterTransition = transition;
+ }
}
/**
@@ -1993,7 +1993,10 @@
* @attr ref android.R.styleable#Fragment_fragmentEnterTransition
*/
public Transition getEnterTransition() {
- return mEnterTransition;
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mEnterTransition;
}
/**
@@ -2011,7 +2014,9 @@
* @attr ref android.R.styleable#Fragment_fragmentExitTransition
*/
public void setReturnTransition(Transition transition) {
- mReturnTransition = transition;
+ if (shouldChangeTransition(transition, USE_DEFAULT_TRANSITION)) {
+ ensureAnimationInfo().mReturnTransition = transition;
+ }
}
/**
@@ -2028,8 +2033,11 @@
* @attr ref android.R.styleable#Fragment_fragmentExitTransition
*/
public Transition getReturnTransition() {
- return mReturnTransition == USE_DEFAULT_TRANSITION ? getEnterTransition()
- : mReturnTransition;
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mReturnTransition == USE_DEFAULT_TRANSITION ? getEnterTransition()
+ : mAnimationInfo.mReturnTransition;
}
/**
@@ -2046,7 +2054,9 @@
* @attr ref android.R.styleable#Fragment_fragmentExitTransition
*/
public void setExitTransition(Transition transition) {
- mExitTransition = transition;
+ if (shouldChangeTransition(transition, null)) {
+ ensureAnimationInfo().mExitTransition = transition;
+ }
}
/**
@@ -2063,7 +2073,10 @@
* @attr ref android.R.styleable#Fragment_fragmentExitTransition
*/
public Transition getExitTransition() {
- return mExitTransition;
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mExitTransition;
}
/**
@@ -2080,7 +2093,9 @@
* @attr ref android.R.styleable#Fragment_fragmentReenterTransition
*/
public void setReenterTransition(Transition transition) {
- mReenterTransition = transition;
+ if (shouldChangeTransition(transition, USE_DEFAULT_TRANSITION)) {
+ ensureAnimationInfo().mReenterTransition = transition;
+ }
}
/**
@@ -2097,8 +2112,11 @@
* @attr ref android.R.styleable#Fragment_fragmentReenterTransition
*/
public Transition getReenterTransition() {
- return mReenterTransition == USE_DEFAULT_TRANSITION ? getExitTransition()
- : mReenterTransition;
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mReenterTransition == USE_DEFAULT_TRANSITION ? getExitTransition()
+ : mAnimationInfo.mReenterTransition;
}
/**
@@ -2112,7 +2130,9 @@
* @attr ref android.R.styleable#Fragment_fragmentSharedElementEnterTransition
*/
public void setSharedElementEnterTransition(Transition transition) {
- mSharedElementEnterTransition = transition;
+ if (shouldChangeTransition(transition, null)) {
+ ensureAnimationInfo().mSharedElementEnterTransition = transition;
+ }
}
/**
@@ -2126,7 +2146,10 @@
* @attr ref android.R.styleable#Fragment_fragmentSharedElementEnterTransition
*/
public Transition getSharedElementEnterTransition() {
- return mSharedElementEnterTransition;
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mSharedElementEnterTransition;
}
/**
@@ -2143,7 +2166,9 @@
* @attr ref android.R.styleable#Fragment_fragmentSharedElementReturnTransition
*/
public void setSharedElementReturnTransition(Transition transition) {
- mSharedElementReturnTransition = transition;
+ if (shouldChangeTransition(transition, USE_DEFAULT_TRANSITION)) {
+ ensureAnimationInfo().mSharedElementReturnTransition = transition;
+ }
}
/**
@@ -2160,8 +2185,12 @@
* @attr ref android.R.styleable#Fragment_fragmentSharedElementReturnTransition
*/
public Transition getSharedElementReturnTransition() {
- return mSharedElementReturnTransition == USE_DEFAULT_TRANSITION ?
- getSharedElementEnterTransition() : mSharedElementReturnTransition;
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mSharedElementReturnTransition == USE_DEFAULT_TRANSITION
+ ? getSharedElementEnterTransition()
+ : mAnimationInfo.mSharedElementReturnTransition;
}
/**
@@ -2174,7 +2203,7 @@
* @attr ref android.R.styleable#Fragment_fragmentAllowEnterTransitionOverlap
*/
public void setAllowEnterTransitionOverlap(boolean allow) {
- mAllowEnterTransitionOverlap = allow;
+ ensureAnimationInfo().mAllowEnterTransitionOverlap = allow;
}
/**
@@ -2187,7 +2216,8 @@
* @attr ref android.R.styleable#Fragment_fragmentAllowEnterTransitionOverlap
*/
public boolean getAllowEnterTransitionOverlap() {
- return (mAllowEnterTransitionOverlap == null) ? true : mAllowEnterTransitionOverlap;
+ return (mAnimationInfo == null || mAnimationInfo.mAllowEnterTransitionOverlap == null)
+ ? true : mAnimationInfo.mAllowEnterTransitionOverlap;
}
/**
@@ -2200,7 +2230,7 @@
* @attr ref android.R.styleable#Fragment_fragmentAllowReturnTransitionOverlap
*/
public void setAllowReturnTransitionOverlap(boolean allow) {
- mAllowReturnTransitionOverlap = allow;
+ ensureAnimationInfo().mAllowReturnTransitionOverlap = allow;
}
/**
@@ -2213,7 +2243,90 @@
* @attr ref android.R.styleable#Fragment_fragmentAllowReturnTransitionOverlap
*/
public boolean getAllowReturnTransitionOverlap() {
- return (mAllowReturnTransitionOverlap == null) ? true : mAllowReturnTransitionOverlap;
+ return (mAnimationInfo == null || mAnimationInfo.mAllowReturnTransitionOverlap == null)
+ ? true : mAnimationInfo.mAllowReturnTransitionOverlap;
+ }
+
+ /**
+ * Postpone the entering Fragment transition until {@link #startPostponedEnterTransition()}
+ * or {@link FragmentManager#executePendingTransactions()} has been called.
+ * <p>
+ * This method gives the Fragment the ability to delay Fragment animations
+ * until all data is loaded. Until then, the added, shown, and
+ * attached Fragments will be INVISIBLE and removed, hidden, and detached Fragments won't
+ * be have their Views removed. The transaction runs when all postponed added Fragments in the
+ * transaction have called {@link #startPostponedEnterTransition()}.
+ * <p>
+ * This method should be called before being added to the FragmentTransaction or
+ * in {@link #onCreate(Bundle), {@link #onAttach(Context)}, or
+ * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}}.
+ * {@link #startPostponedEnterTransition()} must be called to allow the Fragment to
+ * start the transitions.
+ * <p>
+ * When a FragmentTransaction is started that may affect a postponed FragmentTransaction,
+ * based on which containers are in their operations, the postponed FragmentTransaction
+ * will have its start triggered. The early triggering may result in faulty or nonexistent
+ * animations in the postponed transaction. FragmentTransactions that operate only on
+ * independent containers will not interfere with each other's postponement.
+ * <p>
+ * Calling postponeEnterTransition on Fragments with a null View will not postpone the
+ * transition. Likewise, postponement only works if FragmentTransaction optimizations are
+ * enabled.
+ *
+ * @see Activity#postponeEnterTransition()
+ * @see FragmentTransaction#setAllowOptimization(boolean)
+ */
+ public void postponeEnterTransition() {
+ ensureAnimationInfo().mEnterTransitionPostponed = true;
+ }
+
+ /**
+ * Begin postponed transitions after {@link #postponeEnterTransition()} was called.
+ * If postponeEnterTransition() was called, you must call startPostponedEnterTransition()
+ * or {@link FragmentManager#executePendingTransactions()} to complete the FragmentTransaction.
+ * If postponement was interrupted with {@link FragmentManager#executePendingTransactions()},
+ * before {@code startPostponedEnterTransition()}, animations may not run or may execute
+ * improperly.
+ *
+ * @see Activity#startPostponedEnterTransition()
+ */
+ public void startPostponedEnterTransition() {
+ if (mFragmentManager == null || mFragmentManager.mHost == null) {
+ ensureAnimationInfo().mEnterTransitionPostponed = false;
+ } else if (Looper.myLooper() != mFragmentManager.mHost.getHandler().getLooper()) {
+ mFragmentManager.mHost.getHandler().
+ postAtFrontOfQueue(this::callStartTransitionListener);
+ } else {
+ callStartTransitionListener();
+ }
+ }
+
+ /**
+ * Calls the start transition listener. This must be called on the UI thread.
+ */
+ private void callStartTransitionListener() {
+ final OnStartEnterTransitionListener listener;
+ if (mAnimationInfo == null) {
+ listener = null;
+ } else {
+ mAnimationInfo.mEnterTransitionPostponed = false;
+ listener = mAnimationInfo.mStartEnterTransitionListener;
+ mAnimationInfo.mStartEnterTransitionListener = null;
+ }
+ if (listener != null) {
+ listener.onStartEnterTransition();
+ }
+ }
+
+ /**
+ * Returns true if mAnimationInfo is not null or the transition differs from the default value.
+ * This is broken out to ensure mAnimationInfo is properly locked when checking.
+ */
+ private boolean shouldChangeTransition(Transition transition, Transition defaultValue) {
+ if (transition == defaultValue) {
+ return mAnimationInfo != null;
+ }
+ return true;
}
/**
@@ -2274,8 +2387,8 @@
writer.print(" mTargetRequestCode=");
writer.println(mTargetRequestCode);
}
- if (mNextAnim != 0) {
- writer.print(prefix); writer.print("mNextAnim="); writer.println(mNextAnim);
+ if (getNextAnim() != 0) {
+ writer.print(prefix); writer.print("mNextAnim="); writer.println(getNextAnim());
}
if (mContainer != null) {
writer.print(prefix); writer.print("mContainer="); writer.println(mContainer);
@@ -2283,10 +2396,11 @@
if (mView != null) {
writer.print(prefix); writer.print("mView="); writer.println(mView);
}
- if (mAnimatingAway != null) {
- writer.print(prefix); writer.print("mAnimatingAway="); writer.println(mAnimatingAway);
+ if (getAnimatingAway() != null) {
+ writer.print(prefix); writer.print("mAnimatingAway=");
+ writer.println(getAnimatingAway());
writer.print(prefix); writer.print("mStateAfterAnimating=");
- writer.println(mStateAfterAnimating);
+ writer.println(getStateAfterAnimating());
}
if (mLoaderManager != null) {
writer.print(prefix); writer.println("Loader Manager:");
@@ -2613,6 +2727,23 @@
}
}
+ void setOnStartEnterTransitionListener(OnStartEnterTransitionListener listener) {
+ ensureAnimationInfo();
+ if (listener == mAnimationInfo.mStartEnterTransitionListener) {
+ return;
+ }
+ if (listener != null && mAnimationInfo.mStartEnterTransitionListener != null) {
+ throw new IllegalStateException("Trying to set a replacement " +
+ "startPostponedEnterTransition on " + this);
+ }
+ if (mAnimationInfo.mEnterTransitionPostponed) {
+ mAnimationInfo.mStartEnterTransitionListener = listener;
+ }
+ if (listener != null) {
+ listener.startListening();
+ }
+ }
+
private static Transition loadTransition(Context context, TypedArray typedArray,
Transition currentValue, Transition defaultValue, int id) {
if (currentValue != defaultValue) {
@@ -2631,4 +2762,147 @@
return transition;
}
+ private AnimationInfo ensureAnimationInfo() {
+ if (mAnimationInfo == null) {
+ mAnimationInfo = new AnimationInfo();
+ }
+ return mAnimationInfo;
+ }
+
+ int getNextAnim() {
+ if (mAnimationInfo == null) {
+ return 0;
+ }
+ return mAnimationInfo.mNextAnim;
+ }
+
+ void setNextAnim(int animResourceId) {
+ if (mAnimationInfo == null && animResourceId == 0) {
+ return; // no change!
+ }
+ ensureAnimationInfo().mNextAnim = animResourceId;
+ }
+
+ int getNextTransition() {
+ if (mAnimationInfo == null) {
+ return 0;
+ }
+ return mAnimationInfo.mNextTransition;
+ }
+
+ void setNextTransition(int nextTransition, int nextTransitionStyle) {
+ if (mAnimationInfo == null && nextTransition == 0 && nextTransitionStyle == 0) {
+ return; // no change!
+ }
+ ensureAnimationInfo();
+ mAnimationInfo.mNextTransition = nextTransition;
+ mAnimationInfo.mNextTransitionStyle = nextTransitionStyle;
+ }
+
+ int getNextTransitionStyle() {
+ if (mAnimationInfo == null) {
+ return 0;
+ }
+ return mAnimationInfo.mNextTransitionStyle;
+ }
+
+ SharedElementCallback getEnterTransitionCallback() {
+ if (mAnimationInfo == null) {
+ return SharedElementCallback.NULL_CALLBACK;
+ }
+ return mAnimationInfo.mEnterTransitionCallback;
+ }
+
+ SharedElementCallback getExitTransitionCallback() {
+ if (mAnimationInfo == null) {
+ return SharedElementCallback.NULL_CALLBACK;
+ }
+ return mAnimationInfo.mExitTransitionCallback;
+ }
+
+ Animator getAnimatingAway() {
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mAnimatingAway;
+ }
+
+ void setAnimatingAway(Animator animator) {
+ ensureAnimationInfo().mAnimatingAway = animator;
+ }
+
+ int getStateAfterAnimating() {
+ if (mAnimationInfo == null) {
+ return 0;
+ }
+ return mAnimationInfo.mStateAfterAnimating;
+ }
+
+ void setStateAfterAnimating(int state) {
+ ensureAnimationInfo().mStateAfterAnimating = state;
+ }
+
+ boolean isPostponed() {
+ if (mAnimationInfo == null) {
+ return false;
+ }
+ return mAnimationInfo.mEnterTransitionPostponed;
+ }
+
+ /**
+ * Used internally to be notified when {@link #startPostponedEnterTransition()} has
+ * been called. This listener will only be called once and then be removed from the
+ * listeners.
+ */
+ interface OnStartEnterTransitionListener {
+ void onStartEnterTransition();
+ void startListening();
+ }
+
+ /**
+ * Contains all the animation and transition information for a fragment. This will only
+ * be instantiated for Fragments that have Views.
+ */
+ static class AnimationInfo {
+ // Non-null if the fragment's view hierarchy is currently animating away,
+ // meaning we need to wait a bit on completely destroying it. This is the
+ // animation that is running.
+ Animator mAnimatingAway;
+
+ // If mAnimatingAway != null, this is the state we should move to once the
+ // animation is done.
+ int mStateAfterAnimating;
+
+ // If app has requested a specific animation, this is the one to use.
+ int mNextAnim;
+
+ // If app has requested a specific transition, this is the one to use.
+ int mNextTransition;
+
+ // If app has requested a specific transition style, this is the one to use.
+ int mNextTransitionStyle;
+
+ private Transition mEnterTransition = null;
+ private Transition mReturnTransition = USE_DEFAULT_TRANSITION;
+ private Transition mExitTransition = null;
+ private Transition mReenterTransition = USE_DEFAULT_TRANSITION;
+ private Transition mSharedElementEnterTransition = null;
+ private Transition mSharedElementReturnTransition = USE_DEFAULT_TRANSITION;
+ private Boolean mAllowReturnTransitionOverlap;
+ private Boolean mAllowEnterTransitionOverlap;
+
+ SharedElementCallback mEnterTransitionCallback = SharedElementCallback.NULL_CALLBACK;
+ SharedElementCallback mExitTransitionCallback = SharedElementCallback.NULL_CALLBACK;
+
+ // True when postponeEnterTransition has been called and startPostponeEnterTransition
+ // hasn't been called yet.
+ boolean mEnterTransitionPostponed;
+
+ // Listener to wait for startPostponeEnterTransition. After being called, it will
+ // be set to null
+ OnStartEnterTransitionListener mStartEnterTransitionListener;
+
+ // True if the View was added, and its animation has yet to be run.
+ boolean mIsNewlyAdded;
+ }
}
diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java
index d869168..7e415e9 100644
--- a/core/java/android/app/FragmentHostCallback.java
+++ b/core/java/android/app/FragmentHostCallback.java
@@ -54,7 +54,8 @@
private boolean mLoadersStarted;
public FragmentHostCallback(Context context, Handler handler, int windowAnimations) {
- this(null /*activity*/, context, handler, windowAnimations);
+ this((context instanceof Activity) ? (Activity)context : null, context,
+ chooseHandler(context, handler), windowAnimations);
}
FragmentHostCallback(Activity activity) {
@@ -70,6 +71,19 @@
}
/**
+ * Used internally in {@link #FragmentHostCallback(Context, Handler, int)} to choose
+ * the Activity's handler or the provided handler.
+ */
+ private static Handler chooseHandler(Context context, Handler handler) {
+ if (handler == null && context instanceof Activity) {
+ Activity activity = (Activity) context;
+ return activity.mHandler;
+ } else {
+ return handler;
+ }
+ }
+
+ /**
* Print internal state into the given stream.
*
* @param prefix Desired prefix to prepend at each line of output.
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 674c3f7..9345a03 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -28,7 +28,6 @@
import android.content.res.TypedArray;
import android.os.Bundle;
import android.os.Debug;
-import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
@@ -44,6 +43,7 @@
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+
import com.android.internal.util.FastPrintWriter;
import java.io.FileDescriptor;
@@ -160,6 +160,9 @@
* can call this function (only from the main thread) to do so. Note that
* all callbacks and other related behavior will be done from within this
* call, so be careful about where this is called from.
+ * <p>
+ * This also forces the start of any postponed Transactions where
+ * {@link Fragment#postponeEnterTransition()} has been called.
*
* @return Returns true if there were any pending transactions to be
* executed.
@@ -206,7 +209,7 @@
/**
* Like {@link #popBackStack()}, but performs the operation immediately
* inside of the call. This is like calling {@link #executePendingTransactions()}
- * afterwards.
+ * afterwards without forcing the start of postponed Transactions.
* @return Returns true if there was something popped, else false.
*/
public abstract boolean popBackStackImmediate();
@@ -229,7 +232,7 @@
/**
* Like {@link #popBackStack(String, int)}, but performs the operation immediately
* inside of the call. This is like calling {@link #executePendingTransactions()}
- * afterwards.
+ * afterwards without forcing the start of postponed Transactions.
* @return Returns true if there was something popped, else false.
*/
public abstract boolean popBackStackImmediate(String name, int flags);
@@ -253,7 +256,7 @@
/**
* Like {@link #popBackStack(int, int)}, but performs the operation immediately
* inside of the call. This is like calling {@link #executePendingTransactions()}
- * afterwards.
+ * afterwards without forcing the start of postponed Transactions.
* @return Returns true if there was something popped, else false.
*/
public abstract boolean popBackStackImmediate(int id, int flags);
@@ -445,8 +448,7 @@
}
}
- ArrayList<Runnable> mPendingActions;
- Runnable[] mTmpActions;
+ ArrayList<OpGenerator> mPendingActions;
boolean mExecutingActions;
ArrayList<Fragment> mActive;
@@ -463,7 +465,6 @@
int mCurState = Fragment.INITIALIZING;
FragmentHostCallback<?> mHost;
- FragmentController mController;
FragmentContainer mContainer;
Fragment mParent;
@@ -473,10 +474,18 @@
String mNoTransactionsBecause;
boolean mHavePendingDeferredStart;
+ // Temporary vars for optimizing execution of BackStackRecords:
+ ArrayList<BackStackRecord> mTmpRecords;
+ ArrayList<Boolean> mTmpIsPop;
+ ArrayList<Fragment> mTmpAddedFragments;
+
// Temporary vars for state save and restore.
Bundle mStateBundle = null;
SparseArray<Parcelable> mStateArray = null;
-
+
+ // Postponed transactions.
+ ArrayList<StartEnterTransitionListener> mPostponedTransactions;
+
Runnable mExecCommit = new Runnable() {
@Override
public void run() {
@@ -560,61 +569,73 @@
@Override
public boolean executePendingTransactions() {
- return execPendingActions();
+ boolean updates = execPendingActions();
+ forcePostponedTransactions();
+ return updates;
}
@Override
public void popBackStack() {
- enqueueAction(new Runnable() {
- @Override public void run() {
- popBackStackState(mHost.getHandler(), null, -1, 0);
- }
- }, false);
+ enqueueAction(new PopBackStackState(null, -1, 0), false);
}
@Override
public boolean popBackStackImmediate() {
checkStateLoss();
- executePendingTransactions();
- return popBackStackState(mHost.getHandler(), null, -1, 0);
+ return popBackStackImmediate(null, -1, 0);
}
@Override
- public void popBackStack(final String name, final int flags) {
- enqueueAction(new Runnable() {
- @Override public void run() {
- popBackStackState(mHost.getHandler(), name, -1, flags);
- }
- }, false);
+ public void popBackStack(String name, int flags) {
+ enqueueAction(new PopBackStackState(name, -1, flags), false);
}
@Override
public boolean popBackStackImmediate(String name, int flags) {
checkStateLoss();
- executePendingTransactions();
- return popBackStackState(mHost.getHandler(), name, -1, flags);
+ return popBackStackImmediate(name, -1, flags);
}
@Override
- public void popBackStack(final int id, final int flags) {
+ public void popBackStack(int id, int flags) {
if (id < 0) {
throw new IllegalArgumentException("Bad id: " + id);
}
- enqueueAction(new Runnable() {
- @Override public void run() {
- popBackStackState(mHost.getHandler(), null, id, flags);
- }
- }, false);
+ enqueueAction(new PopBackStackState(null, id, flags), false);
}
@Override
public boolean popBackStackImmediate(int id, int flags) {
checkStateLoss();
- executePendingTransactions();
if (id < 0) {
throw new IllegalArgumentException("Bad id: " + id);
}
- return popBackStackState(mHost.getHandler(), null, id, flags);
+ return popBackStackImmediate(null, id, flags);
+ }
+
+ /**
+ * Used by all public popBackStackImmediate methods, this executes pending transactions and
+ * returns true if the pop action did anything, regardless of what other pending
+ * transactions did.
+ *
+ * @return true if the pop operation did anything or false otherwise.
+ */
+ private boolean popBackStackImmediate(String name, int id, int flags) {
+ execPendingActions();
+ ensureExecReady(true);
+
+ boolean executePop = popBackStackState(mTmpRecords, mTmpIsPop, name, id, flags);
+ if (executePop) {
+ mExecutingActions = true;
+ try {
+ optimizeAndExecuteOps(mTmpRecords, mTmpIsPop);
+ } finally {
+ cleanupExec();
+ }
+ }
+
+ doPendingDeferredStart();
+ return executePop;
}
@Override
@@ -785,7 +806,7 @@
if (N > 0) {
writer.print(prefix); writer.println("Pending Actions:");
for (int i=0; i<N; i++) {
- Runnable r = mPendingActions.get(i);
+ OpGenerator r = mPendingActions.get(i);
writer.print(prefix); writer.print(" #"); writer.print(i);
writer.print(": "); writer.println(r);
}
@@ -817,14 +838,14 @@
Animator loadAnimator(Fragment fragment, int transit, boolean enter,
int transitionStyle) {
- Animator animObj = fragment.onCreateAnimator(transit, enter,
- fragment.mNextAnim);
+ Animator animObj = fragment.onCreateAnimator(transit, enter, fragment.getNextAnim());
if (animObj != null) {
return animObj;
}
- if (fragment.mNextAnim != 0) {
- Animator anim = AnimatorInflater.loadAnimator(mHost.getContext(), fragment.mNextAnim);
+ if (fragment.getNextAnim() != 0) {
+ Animator anim = AnimatorInflater.loadAnimator(mHost.getContext(),
+ fragment.getNextAnim());
if (anim != null) {
return anim;
}
@@ -900,13 +921,13 @@
if (f.mFromLayout && !f.mInLayout) {
return;
}
- if (f.mAnimatingAway != null) {
+ if (f.getAnimatingAway() != null) {
// The fragment is currently being animated... but! Now we
// want to move our state back up. Give up on waiting for the
// animation, move to whatever the final state should be once
// the animation is done, and then we can proceed from there.
- f.mAnimatingAway = null;
- moveToState(f, f.mStateAfterAnimating, 0, 0, true);
+ f.setAnimatingAway(null);
+ moveToState(f, f.getStateAfterAnimating(), 0, 0, true);
}
switch (f.mState) {
case Fragment.INITIALIZING:
@@ -997,16 +1018,13 @@
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
if (container != null) {
- Animator anim = loadAnimator(f, transit, true,
- transitionStyle);
- if (anim != null) {
- anim.setTarget(f.mView);
- setHWLayerAnimListenerIfAlpha(f.mView, anim);
- anim.start();
- }
container.addView(f.mView);
+ f.mIsNewlyAdded = true;
}
- if (f.mHidden) f.mView.setVisibility(View.GONE);
+ if (f.mHidden) {
+ f.mView.setVisibility(View.GONE);
+ f.mIsNewlyAdded = false; // No animation required
+ }
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
}
@@ -1061,7 +1079,8 @@
f.performDestroyView();
if (f.mView != null && f.mContainer != null) {
Animator anim = null;
- if (mCurState > Fragment.INITIALIZING && !mDestroyed) {
+ if (mCurState > Fragment.INITIALIZING && !mDestroyed &&
+ f.mView.getVisibility() == View.VISIBLE) {
anim = loadAnimator(f, transit, false,
transitionStyle);
}
@@ -1070,15 +1089,15 @@
final View view = f.mView;
final Fragment fragment = f;
container.startViewTransition(view);
- f.mAnimatingAway = anim;
- f.mStateAfterAnimating = newState;
+ f.setAnimatingAway(anim);
+ f.setStateAfterAnimating(newState);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator anim) {
container.endViewTransition(view);
- if (fragment.mAnimatingAway != null) {
- fragment.mAnimatingAway = null;
- moveToState(fragment, fragment.mStateAfterAnimating,
+ if (fragment.getAnimatingAway() != null) {
+ fragment.setAnimatingAway(null);
+ moveToState(fragment, fragment.getStateAfterAnimating(),
0, 0, false);
}
}
@@ -1096,24 +1115,24 @@
case Fragment.CREATED:
if (newState < Fragment.CREATED) {
if (mDestroyed) {
- if (f.mAnimatingAway != null) {
+ if (f.getAnimatingAway() != null) {
// The fragment's containing activity is
// being destroyed, but this fragment is
// currently animating away. Stop the
// animation right now -- it is not needed,
// and we can't wait any more on destroying
// the fragment.
- Animator anim = f.mAnimatingAway;
- f.mAnimatingAway = null;
+ Animator anim = f.getAnimatingAway();
+ f.setAnimatingAway(null);
anim.cancel();
}
}
- if (f.mAnimatingAway != null) {
+ if (f.getAnimatingAway() != null) {
// We are waiting for the fragment's view to finish
// animating away. Just make a note of the state
// the fragment now should move to once the animation
// is done.
- f.mStateAfterAnimating = newState;
+ f.setStateAfterAnimating(newState);
newState = Fragment.CREATED;
} else {
if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
@@ -1149,26 +1168,129 @@
moveToState(f, mCurState, 0, 0, false);
}
- void moveToState(int newState, boolean always) {
- moveToState(newState, 0, 0, always);
+ /**
+ * Fragments that have been shown or hidden don't have their visibility changed or
+ * animations run during the {@link #showFragment(Fragment)} or {@link #hideFragment(Fragment)}
+ * calls. After fragments are brought to their final state in
+ * {@link #moveFragmentToExpectedState(Fragment)} the fragments that have been shown or
+ * hidden must have their visibility changed and their animations started here.
+ *
+ * @param fragment The fragment with mHiddenChanged = true that should change its View's
+ * visibility and start the show or hide animation.
+ */
+ void completeShowHideFragment(final Fragment fragment) {
+ if (fragment.mView != null) {
+ Animator anim = loadAnimator(fragment, fragment.getNextTransition(), !fragment.mHidden,
+ fragment.getNextTransitionStyle());
+ if (anim != null) {
+ anim.setTarget(fragment.mView);
+ if (fragment.mHidden) {
+ // Delay the actual hide operation until the animation finishes, otherwise
+ // the fragment will just immediately disappear
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animation.removeListener(this);
+ if (fragment.mView != null) {
+ fragment.mView.setVisibility(View.GONE);
+ }
+ }
+ });
+ }
+ setHWLayerAnimListenerIfAlpha(fragment.mView, anim);
+ anim.start();
+ } else {
+ final int visibility = fragment.mHidden ? View.GONE : View.VISIBLE;
+ fragment.mView.setVisibility(visibility);
+ }
+ }
+ if (fragment.mAdded && fragment.mHasMenu && fragment.mMenuVisible) {
+ mNeedMenuInvalidate = true;
+ }
+ fragment.mHiddenChanged = false;
+ fragment.onHiddenChanged(fragment.mHidden);
}
-
- void moveToState(int newState, int transit, int transitStyle, boolean always) {
+
+ /**
+ * Moves a fragment to its expected final state or the fragment manager's state, depending
+ * on whether the fragment manager's state is raised properly.
+ *
+ * @param f The fragment to change.
+ */
+ void moveFragmentToExpectedState(final Fragment f) {
+ if (f == null) {
+ return;
+ }
+ int nextState = mCurState;
+ if (f.mRemoving) {
+ if (f.isInBackStack()) {
+ nextState = Fragment.CREATED;
+ } else {
+ nextState = Fragment.INITIALIZING;
+ }
+ }
+
+ moveToState(f, nextState, f.getNextTransition(), f.getNextTransitionStyle(), false);
+
+ if (f.mView != null) {
+ // Move the view if it is out of order
+ Fragment underFragment = findFragmentUnder(f);
+ if (underFragment != null) {
+ final View underView = underFragment.mView;
+ // make sure this fragment is in the right order.
+ final ViewGroup container = f.mContainer;
+ int underIndex = container.indexOfChild(underView);
+ int viewIndex = container.indexOfChild(f.mView);
+ if (viewIndex < underIndex) {
+ container.removeViewAt(viewIndex);
+ container.addView(f.mView, underIndex);
+ }
+ }
+ if (f.mIsNewlyAdded && f.mContainer != null) {
+ // Make it visible and run the animations
+ f.mView.setVisibility(View.VISIBLE);
+ f.mIsNewlyAdded = false;
+ // run animations:
+ Animator anim = loadAnimator(f, f.getNextTransition(), true, f.getNextTransitionStyle());
+ if (anim != null) {
+ anim.setTarget(f.mView);
+ setHWLayerAnimListenerIfAlpha(f.mView, anim);
+ anim.start();
+ }
+ }
+ }
+ if (f.mHiddenChanged) {
+ completeShowHideFragment(f);
+ }
+ }
+
+ void moveToState(int newState) {
if (mHost == null && newState != Fragment.INITIALIZING) {
throw new IllegalStateException("No activity");
}
- if (!always && mCurState == newState) {
- return;
- }
-
mCurState = newState;
+
if (mActive != null) {
boolean loadersRunning = false;
- for (int i=0; i<mActive.size(); i++) {
+
+ // Must add them in the proper order. mActive fragments may be out of order
+ final int numAdded = mAdded.size();
+ for (int i = 0; i < numAdded; i++) {
+ Fragment f = mAdded.get(i);
+ moveFragmentToExpectedState(f);
+ if (f.mLoaderManager != null) {
+ loadersRunning |= f.mLoaderManager.hasRunningLoaders();
+ }
+ }
+
+ // Now iterate through all active fragments. These will include those that are removed
+ // and detached.
+ final int numActive = mActive.size();
+ for (int i = 0; i < numActive; i++) {
Fragment f = mActive.get(i);
- if (f != null) {
- moveToState(f, newState, transit, transitStyle, false);
+ if (f != null && (f.mRemoving || f.mDetached) && !f.mIsNewlyAdded) {
+ moveFragmentToExpectedState(f);
if (f.mLoaderManager != null) {
loadersRunning |= f.mLoaderManager.hasRunningLoaders();
}
@@ -1185,7 +1307,7 @@
}
}
}
-
+
void startPendingDeferredFragments() {
if (mActive == null) return;
@@ -1244,6 +1366,9 @@
mAdded.add(fragment);
fragment.mAdded = true;
fragment.mRemoving = false;
+ if (fragment.mView == null) {
+ fragment.mHiddenChanged = false;
+ }
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
}
@@ -1252,8 +1377,8 @@
}
}
}
-
- public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
+
+ public void removeFragment(Fragment fragment) {
if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
final boolean inactive = !fragment.isInBackStack();
if (!fragment.mDetached || inactive) {
@@ -1273,66 +1398,42 @@
}
fragment.mAdded = false;
fragment.mRemoving = true;
- moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
- transition, transitionStyle, false);
}
}
-
- public void hideFragment(Fragment fragment, int transition, int transitionStyle) {
+
+ /**
+ * Marks a fragment as hidden to be later animated in with
+ * {@link #completeShowHideFragment(Fragment)}.
+ *
+ * @param fragment The fragment to be shown.
+ */
+ public void hideFragment(Fragment fragment) {
if (DEBUG) Log.v(TAG, "hide: " + fragment);
if (!fragment.mHidden) {
fragment.mHidden = true;
- if (fragment.mView != null) {
- Animator anim = loadAnimator(fragment, transition, false,
- transitionStyle);
- if (anim != null) {
- anim.setTarget(fragment.mView);
- // Delay the actual hide operation until the animation finishes, otherwise
- // the fragment will just immediately disappear
- final Fragment finalFragment = fragment;
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (finalFragment.mView != null) {
- finalFragment.mView.setVisibility(View.GONE);
- }
- }
- });
- setHWLayerAnimListenerIfAlpha(finalFragment.mView, anim);
- anim.start();
- } else {
- fragment.mView.setVisibility(View.GONE);
- }
- }
- if (fragment.mAdded && fragment.mHasMenu && fragment.mMenuVisible) {
- mNeedMenuInvalidate = true;
- }
- fragment.onHiddenChanged(true);
+ // Toggle hidden changed so that if a fragment goes through show/hide/show
+ // it doesn't go through the animation.
+ fragment.mHiddenChanged = !fragment.mHiddenChanged;
}
}
-
- public void showFragment(Fragment fragment, int transition, int transitionStyle) {
+
+ /**
+ * Marks a fragment as shown to be later animated in with
+ * {@link #completeShowHideFragment(Fragment)}.
+ *
+ * @param fragment The fragment to be shown.
+ */
+ public void showFragment(Fragment fragment) {
if (DEBUG) Log.v(TAG, "show: " + fragment);
if (fragment.mHidden) {
fragment.mHidden = false;
- if (fragment.mView != null) {
- Animator anim = loadAnimator(fragment, transition, true,
- transitionStyle);
- if (anim != null) {
- anim.setTarget(fragment.mView);
- setHWLayerAnimListenerIfAlpha(fragment.mView, anim);
- anim.start();
- }
- fragment.mView.setVisibility(View.VISIBLE);
- }
- if (fragment.mAdded && fragment.mHasMenu && fragment.mMenuVisible) {
- mNeedMenuInvalidate = true;
- }
- fragment.onHiddenChanged(false);
+ // Toggle hidden changed so that if a fragment goes through show/hide/show
+ // it doesn't go through the animation.
+ fragment.mHiddenChanged = !fragment.mHiddenChanged;
}
}
-
- public void detachFragment(Fragment fragment, int transition, int transitionStyle) {
+
+ public void detachFragment(Fragment fragment) {
if (DEBUG) Log.v(TAG, "detach: " + fragment);
if (!fragment.mDetached) {
fragment.mDetached = true;
@@ -1346,12 +1447,11 @@
mNeedMenuInvalidate = true;
}
fragment.mAdded = false;
- moveToState(fragment, Fragment.CREATED, transition, transitionStyle, false);
}
}
}
- public void attachFragment(Fragment fragment, int transition, int transitionStyle) {
+ public void attachFragment(Fragment fragment) {
if (DEBUG) Log.v(TAG, "attach: " + fragment);
if (fragment.mDetached) {
fragment.mDetached = false;
@@ -1368,7 +1468,6 @@
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
}
- moveToState(fragment, mCurState, transition, transitionStyle, false);
}
}
}
@@ -1447,7 +1546,7 @@
* @param allowStateLoss whether to allow loss of state information
* @throws IllegalStateException if the activity has been destroyed
*/
- public void enqueueAction(Runnable action, boolean allowStateLoss) {
+ public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
@@ -1456,10 +1555,25 @@
throw new IllegalStateException("Activity has been destroyed");
}
if (mPendingActions == null) {
- mPendingActions = new ArrayList<Runnable>();
+ mPendingActions = new ArrayList<>();
}
mPendingActions.add(action);
- if (mPendingActions.size() == 1) {
+ scheduleCommit();
+ }
+ }
+
+ /**
+ * Schedules the execution when one hasn't been scheduled already. This should happen
+ * the first time {@link #enqueueAction(OpGenerator, boolean)} is called or when
+ * a postponed transaction has been started with
+ * {@link Fragment#startPostponedEnterTransition()}
+ */
+ private void scheduleCommit() {
+ synchronized (this) {
+ boolean postponeReady =
+ mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
+ boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
+ if (postponeReady || pendingReady) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
}
@@ -1522,7 +1636,13 @@
}
}
- public void execSingleAction(Runnable action, boolean allowStateLoss) {
+ /**
+ * Broken out from exec*, this prepares for gathering and executing operations.
+ *
+ * @param allowStateLoss true if state loss should be ignored or false if it should be
+ * checked.
+ */
+ private void ensureExecReady(boolean allowStateLoss) {
if (mExecutingActions) {
throw new IllegalStateException("FragmentManager is already executing transactions");
}
@@ -1535,55 +1655,50 @@
checkStateLoss();
}
- mExecutingActions = true;
- try {
- action.run();
- } finally {
- mExecutingActions = false;
+ if (mTmpRecords == null) {
+ mTmpRecords = new ArrayList<>();
+ mTmpIsPop = new ArrayList<>();
+ }
+ executePostponedTransaction(null, null);
+ }
+
+ public void execSingleAction(OpGenerator action, boolean allowStateLoss) {
+ ensureExecReady(allowStateLoss);
+ if (action.generateOps(mTmpRecords, mTmpIsPop)) {
+ mExecutingActions = true;
+ try {
+ optimizeAndExecuteOps(mTmpRecords, mTmpIsPop);
+ } finally {
+ cleanupExec();
+ }
}
doPendingDeferredStart();
}
/**
+ * Broken out of exec*, this cleans up the mExecutingActions and the temporary structures
+ * used in executing operations.
+ */
+ private void cleanupExec() {
+ mExecutingActions = false;
+ mTmpIsPop.clear();
+ mTmpRecords.clear();
+ }
+
+ /**
* Only call from main thread!
*/
public boolean execPendingActions() {
- if (mExecutingActions) {
- throw new IllegalStateException("Recursive entry to executePendingTransactions");
- }
-
- if (Looper.myLooper() != mHost.getHandler().getLooper()) {
- throw new IllegalStateException("Must be called from main thread of process");
- }
+ ensureExecReady(true);
boolean didSomething = false;
-
- while (true) {
- int numActions;
-
- synchronized (this) {
- if (mPendingActions == null || mPendingActions.size() == 0) {
- break;
- }
-
- numActions = mPendingActions.size();
- if (mTmpActions == null || mTmpActions.length < numActions) {
- mTmpActions = new Runnable[numActions];
- }
- mPendingActions.toArray(mTmpActions);
- mPendingActions.clear();
- mHost.getHandler().removeCallbacks(mExecCommit);
- }
-
+ while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
mExecutingActions = true;
try {
- for (int i = 0; i < numActions; i++) {
- mTmpActions[i].run();
- mTmpActions[i] = null;
- }
+ optimizeAndExecuteOps(mTmpRecords, mTmpIsPop);
} finally {
- mExecutingActions = false;
+ cleanupExec();
}
didSomething = true;
}
@@ -1593,6 +1708,382 @@
return didSomething;
}
+ /**
+ * Complete the execution of transactions that have previously been postponed, but are
+ * now ready.
+ */
+ private void executePostponedTransaction(ArrayList<BackStackRecord> records,
+ ArrayList<Boolean> isRecordPop) {
+ int numPostponed = mPostponedTransactions == null ? 0 : mPostponedTransactions.size();
+ for (int i = 0; i < numPostponed; i++) {
+ StartEnterTransitionListener listener = mPostponedTransactions.get(i);
+ if (records != null && !listener.mIsBack) {
+ int index = records.indexOf(listener.mRecord);
+ if (index != -1 && isRecordPop.get(index)) {
+ listener.cancelTransaction();
+ continue;
+ }
+ }
+ if (listener.isReady() || (records != null &&
+ listener.mRecord.interactsWith(records, 0, records.size()))) {
+ mPostponedTransactions.remove(i);
+ i--;
+ numPostponed--;
+ int index;
+ if (records != null && !listener.mIsBack &&
+ (index = records.indexOf(listener.mRecord)) != -1 &&
+ isRecordPop.get(index)) {
+ // This is popping a postponed transaction
+ listener.cancelTransaction();
+ } else {
+ listener.completeTransaction();
+ }
+ }
+ }
+ }
+
+ /**
+ * Optimizes BackStackRecord operations. This method merges operations of proximate records
+ * that allow optimization. See {@link FragmentTransaction#setAllowOptimization(boolean)}.
+ * <p>
+ * For example, a transaction that adds to the back stack and then another that pops that
+ * back stack record will be optimized.
+ * <p>
+ * Likewise, two transactions committed that are executed at the same time will be optimized
+ * as well as two pop operations executed together.
+ *
+ * @param records The records pending execution
+ * @param isRecordPop The direction that these records are being run.
+ */
+ private void optimizeAndExecuteOps(ArrayList<BackStackRecord> records,
+ ArrayList<Boolean> isRecordPop) {
+ if (records == null || records.isEmpty()) {
+ return;
+ }
+
+ if (isRecordPop == null || records.size() != isRecordPop.size()) {
+ throw new IllegalStateException("Internal error with the back stack records");
+ }
+
+ // Force start of any postponed transactions that interact with scheduled transactions:
+ executePostponedTransaction(records, isRecordPop);
+
+ final int numRecords = records.size();
+ int startIndex = 0;
+ for (int recordNum = 0; recordNum < numRecords; recordNum++) {
+ final boolean canOptimize = records.get(recordNum).mAllowOptimization;
+ if (!canOptimize) {
+ // execute all previous transactions
+ if (startIndex != recordNum) {
+ executeOpsTogether(records, isRecordPop, startIndex, recordNum);
+ }
+ // execute all unoptimized together
+ int optimizeEnd;
+ for (optimizeEnd = recordNum + 1; optimizeEnd < numRecords; optimizeEnd++) {
+ if (records.get(optimizeEnd).mAllowOptimization) {
+ break;
+ }
+ }
+ executeOpsTogether(records, isRecordPop, recordNum, optimizeEnd);
+ startIndex = optimizeEnd;
+ recordNum = optimizeEnd - 1;
+ }
+ }
+ if (startIndex != numRecords) {
+ executeOpsTogether(records, isRecordPop, startIndex, numRecords);
+ }
+ }
+
+ /**
+ * Optimizes a subset of a list of BackStackRecords, all of which either allow optimization or
+ * do not allow optimization.
+ * @param records A list of BackStackRecords that are to be optimized
+ * @param isRecordPop The direction that these records are being run.
+ * @param startIndex The index of the first record in <code>records</code> to be optimized
+ * @param endIndex One more than the final record index in <code>records</code> to optimize.
+ */
+ private void executeOpsTogether(ArrayList<BackStackRecord> records,
+ ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
+ final boolean allowOptimization = records.get(startIndex).mAllowOptimization;
+ boolean addToBackStack = false;
+ if (mTmpAddedFragments == null) {
+ mTmpAddedFragments = new ArrayList<>();
+ } else {
+ mTmpAddedFragments.clear();
+ }
+ if (mAdded != null) {
+ mTmpAddedFragments.addAll(mAdded);
+ }
+ for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
+ final BackStackRecord record = records.get(recordNum);
+ final boolean isPop = isRecordPop.get(recordNum);
+ if (!isPop) {
+ record.expandReplaceOps(mTmpAddedFragments);
+ }
+ final int bumpAmount = isPop ? -1 : 1;
+ record.bumpBackStackNesting(bumpAmount);
+ addToBackStack = addToBackStack || record.mAddToBackStack;
+ }
+ mTmpAddedFragments.clear();
+
+ if (!allowOptimization) {
+ FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, endIndex,
+ false);
+ }
+ executeOps(records, isRecordPop, startIndex, endIndex);
+
+ int postponeIndex = endIndex;
+ if (allowOptimization) {
+ moveFragmentsToInvisible();
+ postponeIndex = postponePostponableTransactions(records, isRecordPop,
+ startIndex, endIndex);
+ }
+
+ if (postponeIndex != startIndex && allowOptimization) {
+ // need to run something now
+ FragmentTransition.startTransitions(this, records, isRecordPop, startIndex,
+ postponeIndex, true);
+ moveToState(mCurState);
+ }
+
+ for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
+ final BackStackRecord record = records.get(recordNum);
+ final boolean isPop = isRecordPop.get(recordNum);
+ if (isPop && record.mIndex >= 0) {
+ freeBackStackIndex(record.mIndex);
+ record.mIndex = -1;
+ }
+ }
+
+ if (addToBackStack) {
+ reportBackStackChanged();
+ }
+ }
+
+ /**
+ * Examine all transactions and determine which ones are marked as postponed. Those will
+ * have their operations rolled back and moved to the end of the record list (up to endIndex).
+ * It will also add the postponed transaction to the queue.
+ *
+ * @param records A list of BackStackRecords that should be checked.
+ * @param isRecordPop The direction that these records are being run.
+ * @param startIndex The index of the first record in <code>records</code> to be checked
+ * @param endIndex One more than the final record index in <code>records</code> to be checked.
+ * @return The index of the first postponed transaction or endIndex if no transaction was
+ * postponed.
+ */
+ private int postponePostponableTransactions(ArrayList<BackStackRecord> records,
+ ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
+ int postponeIndex = endIndex;
+ for (int i = endIndex - 1; i >= startIndex; i--) {
+ final BackStackRecord record = records.get(i);
+ final boolean isPop = isRecordPop.get(i);
+ boolean isPostponed = record.isPostponed() &&
+ !record.interactsWith(records, i + 1, endIndex);
+ if (isPostponed) {
+ if (mPostponedTransactions == null) {
+ mPostponedTransactions = new ArrayList<>();
+ }
+ StartEnterTransitionListener listener =
+ new StartEnterTransitionListener(record, isPop);
+ mPostponedTransactions.add(listener);
+ record.setOnStartPostponedListener(listener);
+
+ // roll back the transaction
+ if (isPop) {
+ record.executeOps();
+ } else {
+ record.executePopOps();
+ }
+
+ // move to the end
+ postponeIndex--;
+ if (i != postponeIndex) {
+ records.remove(i);
+ records.add(postponeIndex, record);
+ }
+
+ // different views may be visible now
+ moveFragmentsToInvisible();
+ }
+ }
+ return postponeIndex;
+ }
+
+ /**
+ * When a postponed transaction is ready to be started, this completes the transaction,
+ * removing, hiding, or showing views as well as starting the animations and transitions.
+ * <p>
+ * {@code runtransitions} is set to false when the transaction postponement was interrupted
+ * abnormally -- normally by a new transaction being started that affects the postponed
+ * transaction.
+ *
+ * @param record The transaction to run
+ * @param isPop true if record is popping or false if it is adding
+ * @param runTransitions true if the fragment transition should be run or false otherwise.
+ * @param moveToState true if the state should be changed after executing the operations.
+ * This is false when the transaction is canceled when a postponed
+ * transaction is popped.
+ */
+ private void completeExecute(BackStackRecord record, boolean isPop, boolean runTransitions,
+ boolean moveToState) {
+ ArrayList<BackStackRecord> records = new ArrayList<>(1);
+ ArrayList<Boolean> isRecordPop = new ArrayList<>(1);
+ records.add(record);
+ isRecordPop.add(isPop);
+ executeOps(records, isRecordPop, 0, 1);
+ if (runTransitions) {
+ FragmentTransition.startTransitions(this, records, isRecordPop, 0, 1, true);
+ }
+ if (moveToState) {
+ moveToState(mCurState);
+ } else if (mActive != null) {
+ final int numActive = mActive.size();
+ for (int i = 0; i < numActive; i++) {
+ // Allow added fragments to be removed during the pop since we aren't going
+ // to move them to the final state with moveToState(mCurState).
+ Fragment fragment = mActive.get(i);
+ if (fragment.mView != null && fragment.mIsNewlyAdded &&
+ record.interactsWith(fragment.mContainerId)) {
+ fragment.mIsNewlyAdded = false;
+ }
+ }
+ }
+ }
+
+ /**
+ * Find a fragment within the fragment's container whose View should be below the passed
+ * fragment. {@code null} is returned when the fragment has no View or if there should be
+ * no fragment with a View below the given fragment.
+ *
+ * As an example, if mAdded has two Fragments with Views sharing the same container:
+ * FragmentA
+ * FragmentB
+ *
+ * Then, when processing FragmentB, FragmentA will be returned. If, however, FragmentA
+ * had no View, null would be returned.
+ *
+ * @param f The fragment that may be on top of another fragment.
+ * @return The fragment with a View under f, if one exists or null if f has no View or
+ * there are no fragments with Views in the same container.
+ */
+ private Fragment findFragmentUnder(Fragment f) {
+ final ViewGroup container = f.mContainer;
+ final View view = f.mView;
+
+ if (container == null || view == null) {
+ return null;
+ }
+
+ final int fragmentIndex = mAdded.indexOf(f);
+ for (int i = fragmentIndex - 1; i >= 0; i--) {
+ Fragment underFragment = mAdded.get(i);
+ if (underFragment.mContainer == container && underFragment.mView != null) {
+ // Found the fragment under this one
+ return underFragment;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Run the operations in the BackStackRecords, either to push or pop.
+ *
+ * @param records The list of records whose operations should be run.
+ * @param isRecordPop The direction that these records are being run.
+ * @param startIndex The index of the first entry in records to run.
+ * @param endIndex One past the index of the final entry in records to run.
+ */
+ private static void executeOps(ArrayList<BackStackRecord> records,
+ ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
+ for (int i = startIndex; i < endIndex; i++) {
+ final BackStackRecord record = records.get(i);
+ final boolean isPop = isRecordPop.get(i);
+ if (isPop) {
+ record.executePopOps();
+ } else {
+ record.executeOps();
+ }
+ }
+ }
+
+ /**
+ * Ensure that fragments that are added are moved to at least the CREATED state.
+ * Any newly-added Views are made INVISIBLE so that the Transaction can be postponed
+ * with {@link Fragment#postponeEnterTransition()}.
+ */
+ private void moveFragmentsToInvisible() {
+ if (mCurState < Fragment.CREATED) {
+ return;
+ }
+ // We want to leave the fragment in the started state
+ final int state = Math.min(mCurState, Fragment.STARTED);
+ final int numAdded = mAdded == null ? 0 : mAdded.size();
+ for (int i = 0; i < numAdded; i++) {
+ Fragment fragment = mAdded.get(i);
+ if (fragment.mState < state) {
+ moveToState(fragment, state, fragment.getNextAnim(), fragment.getNextTransition(), false);
+ if (fragment.mView != null && !fragment.mHidden && fragment.mIsNewlyAdded) {
+ fragment.mView.setVisibility(View.INVISIBLE);
+ }
+ }
+ }
+ }
+
+ /**
+ * Starts all postponed transactions regardless of whether they are ready or not.
+ */
+ private void forcePostponedTransactions() {
+ if (mPostponedTransactions != null) {
+ while (!mPostponedTransactions.isEmpty()) {
+ mPostponedTransactions.remove(0).completeTransaction();
+ }
+ }
+ }
+
+ /**
+ * Ends the animations of fragments so that they immediately reach the end state.
+ * This is used prior to saving the state so that the correct state is saved.
+ */
+ private void endAnimatingAwayFragments() {
+ final int numFragments = mActive == null ? 0 : mActive.size();
+ for (int i = 0; i < numFragments; i++) {
+ Fragment fragment = mActive.get(i);
+ if (fragment != null && fragment.getAnimatingAway() != null) {
+ // Give up waiting for the animation and just end it.
+ fragment.getAnimatingAway().end();
+ }
+ }
+ }
+
+ /**
+ * Adds all records in the pending actions to records and whether they are add or pop
+ * operations to isPop. After executing, the pending actions will be empty.
+ *
+ * @param records All pending actions will generate BackStackRecords added to this.
+ * This contains the transactions, in order, to execute.
+ * @param isPop All pending actions will generate booleans to add to this. This contains
+ * an entry for each entry in records to indicate whether or not it is a
+ * pop action.
+ */
+ private boolean generateOpsForPendingActions(ArrayList<BackStackRecord> records,
+ ArrayList<Boolean> isPop) {
+ int numActions;
+ synchronized (this) {
+ if (mPendingActions == null || mPendingActions.size() == 0) {
+ return false;
+ }
+
+ numActions = mPendingActions.size();
+ for (int i = 0; i < numActions; i++) {
+ mPendingActions.get(i).generateOps(records, isPop);
+ }
+ mPendingActions.clear();
+ mHost.getHandler().removeCallbacks(mExecCommit);
+ }
+ return numActions > 0;
+ }
+
void doPendingDeferredStart() {
if (mHavePendingDeferredStart) {
boolean loadersRunning = false;
@@ -1624,24 +2115,19 @@
mBackStack.add(state);
reportBackStackChanged();
}
-
- boolean popBackStackState(Handler handler, String name, int id, int flags) {
+
+ boolean popBackStackState(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop,
+ String name, int id, int flags) {
if (mBackStack == null) {
return false;
}
- if (name == null && id < 0 && (flags&POP_BACK_STACK_INCLUSIVE) == 0) {
- int last = mBackStack.size()-1;
+ if (name == null && id < 0 && (flags & POP_BACK_STACK_INCLUSIVE) == 0) {
+ int last = mBackStack.size() - 1;
if (last < 0) {
return false;
}
- final BackStackRecord bss = mBackStack.remove(last);
- SparseArray<BackStackRecord.FragmentContainerTransition> transitioningFragments =
- new SparseArray<>();
- if (mCurState >= Fragment.CREATED) {
- bss.calculateBackFragments(transitioningFragments);
- }
- bss.popFromBackStack(true, null, transitioningFragments);
- reportBackStackChanged();
+ records.add(mBackStack.remove(last));
+ isRecordPop.add(true);
} else {
int index = -1;
if (name != null || id >= 0) {
@@ -1678,25 +2164,10 @@
if (index == mBackStack.size()-1) {
return false;
}
- final ArrayList<BackStackRecord> states
- = new ArrayList<BackStackRecord>();
- for (int i=mBackStack.size()-1; i>index; i--) {
- states.add(mBackStack.remove(i));
+ for (int i = mBackStack.size() - 1; i > index; i--) {
+ records.add(mBackStack.remove(i));
+ isRecordPop.add(true);
}
- final int LAST = states.size()-1;
- SparseArray<BackStackRecord.FragmentContainerTransition> transitioningFragments =
- new SparseArray<>();
- if (mCurState >= Fragment.CREATED) {
- for (int i = 0; i <= LAST; i++) {
- states.get(i).calculateBackFragments(transitioningFragments);
- }
- }
- BackStackRecord.TransitionState state = null;
- for (int i=0; i<=LAST; i++) {
- if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i));
- state = states.get(i).popFromBackStack(i == LAST, state, transitioningFragments);
- }
- reportBackStackChanged();
}
return true;
}
@@ -1795,6 +2266,8 @@
Parcelable saveAllState() {
// Make sure all pending operations have now been executed to get
// our state update-to-date.
+ forcePostponedTransactions();
+ endAnimatingAwayFragments();
execPendingActions();
mStateSaved = true;
@@ -2036,40 +2509,40 @@
public void dispatchCreate() {
mStateSaved = false;
- moveToState(Fragment.CREATED, false);
+ moveToState(Fragment.CREATED);
}
public void dispatchActivityCreated() {
mStateSaved = false;
- moveToState(Fragment.ACTIVITY_CREATED, false);
+ moveToState(Fragment.ACTIVITY_CREATED);
}
public void dispatchStart() {
mStateSaved = false;
- moveToState(Fragment.STARTED, false);
+ moveToState(Fragment.STARTED);
}
public void dispatchResume() {
mStateSaved = false;
- moveToState(Fragment.RESUMED, false);
+ moveToState(Fragment.RESUMED);
}
public void dispatchPause() {
- moveToState(Fragment.STARTED, false);
+ moveToState(Fragment.STARTED);
}
public void dispatchStop() {
- moveToState(Fragment.STOPPED, false);
+ moveToState(Fragment.STOPPED);
}
public void dispatchDestroyView() {
- moveToState(Fragment.CREATED, false);
+ moveToState(Fragment.CREATED);
}
public void dispatchDestroy() {
mDestroyed = true;
execPendingActions();
- moveToState(Fragment.INITIALIZING, false);
+ moveToState(Fragment.INITIALIZING);
mHost = null;
mContainer = null;
mParent = null;
@@ -2363,4 +2836,121 @@
LayoutInflater.Factory2 getLayoutInflaterFactory() {
return this;
}
+
+ /**
+ * An add or pop transaction to be scheduled for the UI thread.
+ */
+ interface OpGenerator {
+ /**
+ * Generate transactions to add to {@code records} and whether or not the transaction is
+ * an add or pop to {@code isRecordPop}.
+ *
+ * records and isRecordPop must be added equally so that each transaction in records
+ * matches the boolean for whether or not it is a pop in isRecordPop.
+ *
+ * @param records A list to add transactions to.
+ * @param isRecordPop A list to add whether or not the transactions added to records is
+ * a pop transaction.
+ * @return true if something was added or false otherwise.
+ */
+ boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop);
+ }
+
+ /**
+ * A pop operation OpGenerator. This will be run on the UI thread and will generate the
+ * transactions that will be popped if anything can be popped.
+ */
+ private class PopBackStackState implements OpGenerator {
+ final String mName;
+ final int mId;
+ final int mFlags;
+
+ public PopBackStackState(String name, int id, int flags) {
+ mName = name;
+ mId = id;
+ mFlags = flags;
+ }
+
+ @Override
+ public boolean generateOps(ArrayList<BackStackRecord> records,
+ ArrayList<Boolean> isRecordPop) {
+ return popBackStackState(records, isRecordPop, mName, mId, mFlags);
+ }
+ }
+
+ /**
+ * A listener for a postponed transaction. This waits until
+ * {@link Fragment#startPostponedEnterTransition()} is called or a transaction is started
+ * that interacts with this one, based on interactions with the fragment container.
+ */
+ static class StartEnterTransitionListener
+ implements Fragment.OnStartEnterTransitionListener {
+ private final boolean mIsBack;
+ private final BackStackRecord mRecord;
+ private int mNumPostponed;
+
+ public StartEnterTransitionListener(BackStackRecord record, boolean isBack) {
+ mIsBack = isBack;
+ mRecord = record;
+ }
+
+ /**
+ * Called from {@link Fragment#startPostponedEnterTransition()}, this decreases the
+ * number of Fragments that are postponed. This may cause the transaction to schedule
+ * to finish running and run transitions and animations.
+ */
+ @Override
+ public void onStartEnterTransition() {
+ mNumPostponed--;
+ if (mNumPostponed != 0) {
+ return;
+ }
+ mRecord.mManager.scheduleCommit();
+ }
+
+ /**
+ * Called from {@link Fragment#
+ * setOnStartEnterTransitionListener(Fragment.OnStartEnterTransitionListener)}, this
+ * increases the number of fragments that are postponed as part of this transaction.
+ */
+ @Override
+ public void startListening() {
+ mNumPostponed++;
+ }
+
+ /**
+ * @return true if there are no more postponed fragments as part of the transaction.
+ */
+ public boolean isReady() {
+ return mNumPostponed == 0;
+ }
+
+ /**
+ * Completes the transaction and start the animations and transitions. This may skip
+ * the transitions if this is called before all fragments have called
+ * {@link Fragment#startPostponedEnterTransition()}.
+ */
+ public void completeTransaction() {
+ final boolean canceled;
+ canceled = mNumPostponed > 0;
+ FragmentManagerImpl manager = mRecord.mManager;
+ final int numAdded = manager.mAdded.size();
+ for (int i = 0; i < numAdded; i++) {
+ final Fragment fragment = manager.mAdded.get(i);
+ fragment.setOnStartEnterTransitionListener(null);
+ if (canceled && fragment.isPostponed()) {
+ fragment.startPostponedEnterTransition();
+ }
+ }
+ mRecord.mManager.completeExecute(mRecord, mIsBack, !canceled, true);
+ }
+
+ /**
+ * Cancels this transaction instead of completing it. That means that the state isn't
+ * changed, so the pop results in no change to the state.
+ */
+ public void cancelTransaction() {
+ mRecord.mManager.completeExecute(mRecord, mIsBack, false, false);
+ }
+ }
}
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 633e85b..25a7839 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -260,6 +260,32 @@
public abstract FragmentTransaction setBreadCrumbShortTitle(CharSequence text);
/**
+ * Sets whether or not to allow optimizing operations within and across
+ * transactions. Optimizing fragment transaction's operations can eliminate
+ * operations that cancel. For example, if two transactions are executed
+ * together, one that adds a fragment A and the next replaces it with fragment B,
+ * the operations will cancel and only fragment B will be added. That means that
+ * fragment A may not go through the creation/destruction lifecycle.
+ * <p>
+ * The side effect of optimization is that fragments may have state changes
+ * out of the expected order. For example, one transaction adds fragment A,
+ * a second adds fragment B, then a third removes fragment A. Without optimization,
+ * fragment B could expect that while it is being created, fragment A will also
+ * exist because fragment A will be removed after fragment B was added.
+ * With optimization, fragment B cannot expect fragment A to exist when
+ * it has been created because fragment A's add/remove will be optimized out.
+ * <p>
+ * The default is {@code false} for applications targeting version
+ * versions prior to O and {@code true} for applications targeting O and
+ * later.
+ *
+ * @param allowOptimization {@code true} to enable optimizing operations
+ * or {@code false} to disable optimizing
+ * operations on this transaction.
+ */
+ public abstract FragmentTransaction setAllowOptimization(boolean allowOptimization);
+
+ /**
* Schedules a commit of this transaction. The commit does
* not happen immediately; it will be scheduled as work on the main thread
* to be done the next time that thread is ready.
diff --git a/core/java/android/app/FragmentTransition.java b/core/java/android/app/FragmentTransition.java
new file mode 100644
index 0000000..6f52114
--- /dev/null
+++ b/core/java/android/app/FragmentTransition.java
@@ -0,0 +1,1330 @@
+/*
+ * Copyright (C) 2016 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 android.app;
+
+import android.graphics.Rect;
+import android.os.Build;
+import android.transition.Transition;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Contains the Fragment Transition functionality for both optimized and unoptimized
+ * Fragment Transactions. With optimized fragment transactions, all Views have been
+ * added to the View hierarchy prior to calling startTransitions. With
+ */
+class FragmentTransition {
+ /**
+ * The inverse of all BackStackRecord operation commands. This assumes that
+ * REPLACE operations have already been replaced by add/remove operations.
+ */
+ private static final int[] INVERSE_OPS = {
+ BackStackRecord.OP_NULL, // inverse of OP_NULL (error)
+ BackStackRecord.OP_REMOVE, // inverse of OP_ADD
+ BackStackRecord.OP_NULL, // inverse of OP_REPLACE (error)
+ BackStackRecord.OP_ADD, // inverse of OP_REMOVE
+ BackStackRecord.OP_SHOW, // inverse of OP_HIDE
+ BackStackRecord.OP_HIDE, // inverse of OP_SHOW
+ BackStackRecord.OP_ATTACH, // inverse of OP_DETACH
+ BackStackRecord.OP_DETACH, // inverse of OP_ATTACH
+ };
+
+ /**
+ * The main entry point for Fragment Transitions, this starts the transitions
+ * set on the leaving Fragment's {@link Fragment#getExitTransition()}, the
+ * entering Fragment's {@link Fragment#getEnterTransition()} and
+ * {@link Fragment#getSharedElementEnterTransition()}. When popping,
+ * the leaving Fragment's {@link Fragment#getReturnTransition()} and
+ * {@link Fragment#getSharedElementReturnTransition()} and the entering
+ * {@link Fragment#getReenterTransition()} will be run.
+ * <p>
+ * With optimized Fragment Transitions, all Views have been added to the
+ * View hierarchy prior to calling this method. The incoming Fragment's Views
+ * will be INVISIBLE. With unoptimized Fragment Transitions, this method
+ * is called before any change has been made to the hierarchy. That means
+ * that the added Fragments have not created their Views yet and the hierarchy
+ * is unknown.
+ *
+ * @param fragmentManager The executing FragmentManagerImpl
+ * @param records The list of transactions being executed.
+ * @param isRecordPop For each transaction, whether it is a pop transaction or not.
+ * @param startIndex The first index into records and isRecordPop to execute as
+ * part of this transition.
+ * @param endIndex One past the last index into records and isRecordPop to execute
+ * as part of this transition.
+ * @param isOptimized true if this is an optimized transaction, meaning that the
+ * Views of incoming fragments have been added. false if the
+ * transaction has yet to be run and Views haven't been created.
+ */
+ static void startTransitions(FragmentManagerImpl fragmentManager,
+ ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop,
+ int startIndex, int endIndex, boolean isOptimized) {
+ if (fragmentManager.mCurState < Fragment.CREATED) {
+ return;
+ }
+ SparseArray<FragmentContainerTransition> transitioningFragments =
+ new SparseArray<>();
+ for (int i = startIndex; i < endIndex; i++) {
+ final BackStackRecord record = records.get(i);
+ final boolean isPop = isRecordPop.get(i);
+ if (isPop) {
+ calculatePopFragments(record, transitioningFragments, isOptimized);
+ } else {
+ calculateFragments(record, transitioningFragments, isOptimized);
+ }
+ }
+
+ if (transitioningFragments.size() != 0) {
+ final View nonExistentView = new View(fragmentManager.mHost.getContext());
+ final int numContainers = transitioningFragments.size();
+ for (int i = 0; i < numContainers; i++) {
+ int containerId = transitioningFragments.keyAt(i);
+ ArrayMap<String, String> nameOverrides = calculateNameOverrides(containerId,
+ records, isRecordPop, startIndex, endIndex);
+
+ FragmentContainerTransition containerTransition = transitioningFragments.valueAt(i);
+
+ if (isOptimized) {
+ configureTransitionsOptimized(fragmentManager, containerId,
+ containerTransition, nonExistentView, nameOverrides);
+ } else {
+ configureTransitionsUnoptimized(fragmentManager, containerId,
+ containerTransition, nonExistentView, nameOverrides);
+ }
+ }
+ }
+ }
+
+ /**
+ * Iterates through the transactions that affect a given fragment container
+ * and tracks the shared element names across transactions. This is most useful
+ * in pop transactions where the names of shared elements are known.
+ *
+ * @param containerId The container ID that is executing the transition.
+ * @param records The list of transactions being executed.
+ * @param isRecordPop For each transaction, whether it is a pop transaction or not.
+ * @param startIndex The first index into records and isRecordPop to execute as
+ * part of this transition.
+ * @param endIndex One past the last index into records and isRecordPop to execute
+ * as part of this transition.
+ * @return A map from the initial shared element name to the final shared element name
+ * before any onMapSharedElements is run.
+ */
+ private static ArrayMap<String, String> calculateNameOverrides(int containerId,
+ ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop,
+ int startIndex, int endIndex) {
+ ArrayMap<String, String> nameOverrides = new ArrayMap<>();
+ for (int recordNum = endIndex - 1; recordNum >= startIndex; recordNum--) {
+ final BackStackRecord record = records.get(recordNum);
+ if (!record.interactsWith(containerId)) {
+ continue;
+ }
+ final boolean isPop = isRecordPop.get(recordNum);
+ if (record.mSharedElementSourceNames != null) {
+ final int numSharedElements = record.mSharedElementSourceNames.size();
+ final ArrayList<String> sources;
+ final ArrayList<String> targets;
+ if (isPop) {
+ targets = record.mSharedElementSourceNames;
+ sources = record.mSharedElementTargetNames;
+ } else {
+ sources = record.mSharedElementSourceNames;
+ targets = record.mSharedElementTargetNames;
+ }
+ for (int i = 0; i < numSharedElements; i++) {
+ String sourceName = sources.get(i);
+ String targetName = targets.get(i);
+ String previousTarget = nameOverrides.remove(targetName);
+ if (previousTarget != null) {
+ nameOverrides.put(sourceName, previousTarget);
+ } else {
+ nameOverrides.put(sourceName, targetName);
+ }
+ }
+ }
+ }
+ return nameOverrides;
+ }
+
+ /**
+ * Configures a transition for a single fragment container for which the transaction was
+ * optimized. That means that all Fragment Views have been added and incoming fragment
+ * Views are marked invisible.
+ *
+ * @param fragmentManager The executing FragmentManagerImpl
+ * @param containerId The container ID that is executing the transition.
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @param nonExistentView A View that does not exist in the hierarchy. This is used to
+ * prevent transitions from acting on other Views when there is no
+ * other target.
+ * @param nameOverrides A map of the shared element names from the starting fragment to
+ * the final fragment's Views as given in
+ * {@link FragmentTransaction#addSharedElement(View, String)}.
+ */
+ private static void configureTransitionsOptimized(FragmentManagerImpl fragmentManager,
+ int containerId, FragmentContainerTransition fragments,
+ View nonExistentView, ArrayMap<String, String> nameOverrides) {
+ ViewGroup sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+ if (sceneRoot == null) {
+ return;
+ }
+ final Fragment inFragment = fragments.lastIn;
+ final Fragment outFragment = fragments.firstOut;
+ final boolean inIsPop = fragments.lastInIsPop;
+ final boolean outIsPop = fragments.firstOutIsPop;
+
+ ArrayList<View> sharedElementsIn = new ArrayList<>();
+ ArrayList<View> sharedElementsOut = new ArrayList<>();
+ Transition enterTransition = getEnterTransition(inFragment, inIsPop);
+ Transition exitTransition = getExitTransition(outFragment, outIsPop);
+
+ TransitionSet sharedElementTransition = configureSharedElementsOptimized(sceneRoot,
+ nonExistentView, nameOverrides, fragments, sharedElementsOut, sharedElementsIn,
+ enterTransition, exitTransition);
+
+ if (enterTransition == null && sharedElementTransition == null &&
+ exitTransition == null) {
+ return; // no transitions!
+ }
+
+ ArrayList<View> exitingViews = configureEnteringExitingViews(exitTransition,
+ outFragment, sharedElementsOut, nonExistentView);
+
+ ArrayList<View> enteringViews = configureEnteringExitingViews(enterTransition,
+ inFragment, sharedElementsIn, nonExistentView);
+
+ setViewVisibility(enteringViews, View.INVISIBLE);
+
+ Transition transition = mergeTransitions(enterTransition, exitTransition,
+ sharedElementTransition, inFragment, inIsPop);
+
+ if (transition != null) {
+ transition.setNameOverrides(nameOverrides);
+ scheduleRemoveTargets(transition,
+ enterTransition, enteringViews, exitTransition, exitingViews,
+ sharedElementTransition, sharedElementsIn);
+ TransitionManager.beginDelayedTransition(sceneRoot, transition);
+ setViewVisibility(enteringViews, View.VISIBLE);
+ // Swap the shared element targets
+ if (sharedElementTransition != null) {
+ sharedElementTransition.getTargets().clear();
+ sharedElementTransition.getTargets().addAll(sharedElementsIn);
+ replaceTargets(sharedElementTransition, sharedElementsOut, sharedElementsIn);
+ }
+ }
+ }
+
+ /**
+ * Configures a transition for a single fragment container for which the transaction was
+ * not optimized. That means that the transaction has not been executed yet, so incoming
+ * Views are not yet known.
+ *
+ * @param fragmentManager The executing FragmentManagerImpl
+ * @param containerId The container ID that is executing the transition.
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @param nonExistentView A View that does not exist in the hierarchy. This is used to
+ * prevent transitions from acting on other Views when there is no
+ * other target.
+ * @param nameOverrides A map of the shared element names from the starting fragment to
+ * the final fragment's Views as given in
+ * {@link FragmentTransaction#addSharedElement(View, String)}.
+ */
+ private static void configureTransitionsUnoptimized(FragmentManagerImpl fragmentManager,
+ int containerId, FragmentContainerTransition fragments,
+ View nonExistentView, ArrayMap<String, String> nameOverrides) {
+ ViewGroup sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+ if (sceneRoot == null) {
+ return;
+ }
+ final Fragment inFragment = fragments.lastIn;
+ final Fragment outFragment = fragments.firstOut;
+ final boolean inIsPop = fragments.lastInIsPop;
+ final boolean outIsPop = fragments.firstOutIsPop;
+
+ Transition enterTransition = getEnterTransition(inFragment, inIsPop);
+ Transition exitTransition = getExitTransition(outFragment, outIsPop);
+
+ ArrayList<View> sharedElementsOut = new ArrayList<>();
+ ArrayList<View> sharedElementsIn = new ArrayList<>();
+
+ TransitionSet sharedElementTransition = configureSharedElementsUnoptimized(sceneRoot,
+ nonExistentView, nameOverrides, fragments, sharedElementsOut, sharedElementsIn,
+ enterTransition, exitTransition);
+
+ if (enterTransition == null && sharedElementTransition == null &&
+ exitTransition == null) {
+ return; // no transitions!
+ }
+
+ ArrayList<View> exitingViews = configureEnteringExitingViews(exitTransition,
+ outFragment, sharedElementsOut, nonExistentView);
+
+ if (exitingViews == null || exitingViews.isEmpty()) {
+ exitTransition = null;
+ }
+
+ if (enterTransition != null) {
+ // Ensure the entering transition doesn't target anything until the views are made
+ // visible
+ enterTransition.addTarget(nonExistentView);
+ }
+
+ Transition transition = mergeTransitions(enterTransition, exitTransition,
+ sharedElementTransition, inFragment, fragments.lastInIsPop);
+
+ if (transition != null) {
+ transition.setNameOverrides(nameOverrides);
+ final ArrayList<View> enteringViews = new ArrayList<>();
+ scheduleRemoveTargets(transition,
+ enterTransition, enteringViews, exitTransition, exitingViews,
+ sharedElementTransition, sharedElementsIn);
+ scheduleTargetChange(sceneRoot, inFragment, nonExistentView, sharedElementsIn,
+ enterTransition, enteringViews, exitTransition, exitingViews);
+
+ TransitionManager.beginDelayedTransition(sceneRoot, transition);
+ }
+ }
+
+ /**
+ * This method is used for fragment transitions for unoptimized transactions to change the
+ * enter and exit transition targets after the call to
+ * {@link TransitionManager#beginDelayedTransition(ViewGroup, Transition)}. The exit transition
+ * must ensure that it does not target any Views and the enter transition must start targeting
+ * the Views of the incoming Fragment.
+ *
+ * @param sceneRoot The fragment container View
+ * @param inFragment The last fragment that is entering
+ * @param nonExistentView A view that does not exist in the hierarchy that is used as a
+ * transition target to ensure no View is targeted.
+ * @param sharedElementsIn The shared element Views of the incoming fragment
+ * @param enterTransition The enter transition of the incoming fragment
+ * @param enteringViews The entering Views of the incoming fragment
+ * @param exitTransition The exit transition of the outgoing fragment
+ * @param exitingViews The exiting views of the outgoing fragment
+ */
+ private static void scheduleTargetChange(final ViewGroup sceneRoot,
+ final Fragment inFragment, final View nonExistentView,
+ final ArrayList<View> sharedElementsIn,
+ final Transition enterTransition, final ArrayList<View> enteringViews,
+ final Transition exitTransition, final ArrayList<View> exitingViews) {
+
+ sceneRoot.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+
+ if (enterTransition != null) {
+ enterTransition.removeTarget(nonExistentView);
+ ArrayList<View> views = configureEnteringExitingViews(
+ enterTransition, inFragment, sharedElementsIn, nonExistentView);
+ enteringViews.addAll(views);
+ }
+
+ if (exitingViews != null) {
+ ArrayList<View> tempExiting = new ArrayList<>();
+ tempExiting.add(nonExistentView);
+ replaceTargets(exitTransition, exitingViews, tempExiting);
+ exitingViews.clear();
+ exitingViews.add(nonExistentView);
+ }
+
+ return true;
+ }
+ });
+ }
+
+ /**
+ * Returns a TransitionSet containing the shared element transition. The wrapping TransitionSet
+ * targets all shared elements to ensure that no other Views are targeted. The shared element
+ * transition can then target any or all shared elements without worrying about accidentally
+ * targeting entering or exiting Views.
+ *
+ * @param inFragment The incoming fragment
+ * @param outFragment the outgoing fragment
+ * @param isPop True if this is a pop transaction or false if it is a normal (add) transaction.
+ * @return A TransitionSet wrapping the shared element transition or null if no such transition
+ * exists.
+ */
+ private static TransitionSet getSharedElementTransition(Fragment inFragment,
+ Fragment outFragment, boolean isPop) {
+ if (inFragment == null || outFragment == null) {
+ return null;
+ }
+ Transition transition = cloneTransition(isPop
+ ? outFragment.getSharedElementReturnTransition()
+ : inFragment.getSharedElementEnterTransition());
+ if (transition == null) {
+ return null;
+ }
+ TransitionSet transitionSet = new TransitionSet();
+ transitionSet.addTransition(transition);
+ return transitionSet;
+ }
+
+ /**
+ * Returns a clone of the enter transition or null if no such transition exists.
+ */
+ private static Transition getEnterTransition(Fragment inFragment, boolean isPop) {
+ if (inFragment == null) {
+ return null;
+ }
+ return cloneTransition(isPop ? inFragment.getReenterTransition() :
+ inFragment.getEnterTransition());
+ }
+
+ /**
+ * Returns a clone of the exit transition or null if no such transition exists.
+ */
+ private static Transition getExitTransition(Fragment outFragment, boolean isPop) {
+ if (outFragment == null) {
+ return null;
+ }
+ return cloneTransition(isPop ? outFragment.getReturnTransition() :
+ outFragment.getExitTransition());
+ }
+
+ /**
+ * Returns a clone of a transition or null if it is null
+ */
+ private static Transition cloneTransition(Transition transition) {
+ if (transition != null) {
+ transition = transition.clone();
+ }
+ return transition;
+ }
+
+ /**
+ * Configures the shared elements of an optimized fragment transaction's transition.
+ * This retrieves the shared elements of the outgoing and incoming fragments, maps the
+ * views, and sets up the epicenter on the transitions.
+ * <p>
+ * The epicenter of exit and shared element transitions is the first shared element
+ * in the outgoing fragment. The epicenter of the entering transition is the first shared
+ * element in the incoming fragment.
+ *
+ * @param sceneRoot The fragment container View
+ * @param nonExistentView A View that does not exist in the hierarchy. This is used to
+ * prevent transitions from acting on other Views when there is no
+ * other target.
+ * @param nameOverrides A map of the shared element names from the starting fragment to
+ * the final fragment's Views as given in
+ * {@link FragmentTransaction#addSharedElement(View, String)}.
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @param sharedElementsOut A list modified to contain the shared elements in the outgoing
+ * fragment
+ * @param sharedElementsIn A list modified to contain the shared elements in the incoming
+ * fragment
+ * @param enterTransition The transition used for entering Views, modified by applying the
+ * epicenter
+ * @param exitTransition The transition used for exiting Views, modified by applying the
+ * epicenter
+ * @return The shared element transition or null if no shared elements exist
+ */
+ private static TransitionSet configureSharedElementsOptimized(final ViewGroup sceneRoot,
+ final View nonExistentView, ArrayMap<String, String> nameOverrides,
+ final FragmentContainerTransition fragments,
+ final ArrayList<View> sharedElementsOut,
+ final ArrayList<View> sharedElementsIn,
+ final Transition enterTransition, final Transition exitTransition) {
+ final Fragment inFragment = fragments.lastIn;
+ final Fragment outFragment = fragments.firstOut;
+ if (inFragment != null) {
+ inFragment.getView().setVisibility(View.VISIBLE);
+ }
+ if (inFragment == null || outFragment == null) {
+ return null; // no shared element without a fragment
+ }
+
+ final boolean inIsPop = fragments.lastInIsPop;
+ TransitionSet sharedElementTransition = nameOverrides.isEmpty() ? null
+ : getSharedElementTransition(inFragment, outFragment, inIsPop);
+
+ ArrayMap<String, View> outSharedElements = captureOutSharedElements(nameOverrides,
+ sharedElementTransition, fragments);
+
+ ArrayMap<String, View> inSharedElements = captureInSharedElements(nameOverrides,
+ sharedElementTransition, fragments);
+
+ if (nameOverrides.isEmpty()) {
+ sharedElementTransition = null;
+ } else {
+ sharedElementsOut.addAll(outSharedElements.values());
+ sharedElementsIn.addAll(inSharedElements.values());
+ }
+
+ if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
+ // don't call onSharedElementStart/End since there is no transition
+ return null;
+ }
+
+ callSharedElementStartEnd(inFragment, outFragment, inIsPop, outSharedElements, true);
+
+ final Rect epicenter;
+ final View epicenterView;
+ if (sharedElementTransition != null) {
+ sharedElementsIn.add(nonExistentView);
+ setSharedElementTargets(sharedElementTransition, nonExistentView, sharedElementsOut);
+ final boolean outIsPop = fragments.firstOutIsPop;
+ final BackStackRecord outTransaction = fragments.firstOutTransaction;
+ setOutEpicenter(sharedElementTransition, exitTransition, outSharedElements, outIsPop,
+ outTransaction);
+ epicenter = new Rect();
+ epicenterView = getInEpicenterView(inSharedElements, fragments,
+ enterTransition, inIsPop);
+ if (epicenterView != null) {
+ enterTransition.setEpicenterCallback(new Transition.EpicenterCallback() {
+ @Override
+ public Rect onGetEpicenter(Transition transition) {
+ return epicenter;
+ }
+ });
+ }
+ } else {
+ epicenter = null;
+ epicenterView = null;
+ }
+
+ sceneRoot.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+ callSharedElementStartEnd(inFragment, outFragment, inIsPop,
+ inSharedElements, false);
+ if (epicenterView != null) {
+ epicenterView.getBoundsOnScreen(epicenter);
+ }
+ return true;
+ }
+ });
+ return sharedElementTransition;
+ }
+
+ /**
+ * Configures the shared elements of an unoptimized fragment transaction's transition.
+ * This retrieves the shared elements of the incoming fragments, and schedules capturing
+ * the incoming fragment's shared elements. It also maps the views, and sets up the epicenter
+ * on the transitions.
+ * <p>
+ * The epicenter of exit and shared element transitions is the first shared element
+ * in the outgoing fragment. The epicenter of the entering transition is the first shared
+ * element in the incoming fragment.
+ *
+ * @param sceneRoot The fragment container View
+ * @param nonExistentView A View that does not exist in the hierarchy. This is used to
+ * prevent transitions from acting on other Views when there is no
+ * other target.
+ * @param nameOverrides A map of the shared element names from the starting fragment to
+ * the final fragment's Views as given in
+ * {@link FragmentTransaction#addSharedElement(View, String)}.
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @param sharedElementsOut A list modified to contain the shared elements in the outgoing
+ * fragment
+ * @param sharedElementsIn A list modified to contain the shared elements in the incoming
+ * fragment
+ * @param enterTransition The transition used for entering Views, modified by applying the
+ * epicenter
+ * @param exitTransition The transition used for exiting Views, modified by applying the
+ * epicenter
+ * @return The shared element transition or null if no shared elements exist
+ */
+ private static TransitionSet configureSharedElementsUnoptimized(final ViewGroup sceneRoot,
+ final View nonExistentView, ArrayMap<String, String> nameOverrides,
+ final FragmentContainerTransition fragments,
+ final ArrayList<View> sharedElementsOut,
+ final ArrayList<View> sharedElementsIn,
+ final Transition enterTransition, final Transition exitTransition) {
+ final Fragment inFragment = fragments.lastIn;
+ final Fragment outFragment = fragments.firstOut;
+
+ if (inFragment == null || outFragment == null) {
+ return null; // no transition
+ }
+
+ final boolean inIsPop = fragments.lastInIsPop;
+ TransitionSet sharedElementTransition = nameOverrides.isEmpty() ? null
+ : getSharedElementTransition(inFragment, outFragment, inIsPop);
+
+ ArrayMap<String, View> outSharedElements = captureOutSharedElements(nameOverrides,
+ sharedElementTransition, fragments);
+
+ if (nameOverrides.isEmpty()) {
+ sharedElementTransition = null;
+ } else {
+ sharedElementsOut.addAll(outSharedElements.values());
+ }
+
+ if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
+ // don't call onSharedElementStart/End since there is no transition
+ return null;
+ }
+
+ callSharedElementStartEnd(inFragment, outFragment, inIsPop, outSharedElements, true);
+
+ final Rect inEpicenter;
+ if (sharedElementTransition != null) {
+ inEpicenter = new Rect();
+ setSharedElementTargets(sharedElementTransition, nonExistentView, sharedElementsOut);
+ final boolean outIsPop = fragments.firstOutIsPop;
+ final BackStackRecord outTransaction = fragments.firstOutTransaction;
+ setOutEpicenter(sharedElementTransition, exitTransition, outSharedElements, outIsPop,
+ outTransaction);
+ if (enterTransition != null) {
+ enterTransition.setEpicenterCallback(new Transition.EpicenterCallback() {
+ @Override
+ public Rect onGetEpicenter(Transition transition) {
+ if (inEpicenter.isEmpty()) {
+ return null;
+ }
+ return inEpicenter;
+ }
+ });
+ }
+ } else {
+ inEpicenter = null;
+ }
+
+ TransitionSet finalSharedElementTransition = sharedElementTransition;
+
+ sceneRoot.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+ ArrayMap<String, View> inSharedElements = captureInSharedElements(
+ nameOverrides, finalSharedElementTransition, fragments);
+
+ if (inSharedElements != null) {
+ sharedElementsIn.addAll(inSharedElements.values());
+ sharedElementsIn.add(nonExistentView);
+ }
+
+ callSharedElementStartEnd(inFragment, outFragment, inIsPop,
+ inSharedElements, false);
+ if (finalSharedElementTransition != null) {
+ finalSharedElementTransition.getTargets().clear();
+ finalSharedElementTransition.getTargets().addAll(sharedElementsIn);
+ replaceTargets(finalSharedElementTransition, sharedElementsOut,
+ sharedElementsIn);
+
+ final View inEpicenterView = getInEpicenterView(inSharedElements,
+ fragments, enterTransition, inIsPop);
+ if (inEpicenterView != null) {
+ inEpicenterView.getBoundsOnScreen(inEpicenter);
+ }
+ }
+ return true;
+ }
+ });
+ return sharedElementTransition;
+ }
+
+ /**
+ * Finds the shared elements in the outgoing fragment. It also calls
+ * {@link SharedElementCallback#onMapSharedElements(List, Map)} to allow more control
+ * of the shared element mapping. {@code nameOverrides} is updated to match the
+ * actual transition name of the mapped shared elements.
+ *
+ * @param nameOverrides A map of the shared element names from the starting fragment to
+ * the final fragment's Views as given in
+ * {@link FragmentTransaction#addSharedElement(View, String)}.
+ * @param sharedElementTransition The shared element transition
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @return The mapping of shared element names to the Views in the hierarchy or null
+ * if there is no shared element transition.
+ */
+ private static ArrayMap<String, View> captureOutSharedElements(
+ ArrayMap<String, String> nameOverrides, TransitionSet sharedElementTransition,
+ FragmentContainerTransition fragments) {
+ if (nameOverrides.isEmpty() || sharedElementTransition == null) {
+ nameOverrides.clear();
+ return null;
+ }
+ final Fragment outFragment = fragments.firstOut;
+ final ArrayMap<String, View> outSharedElements = new ArrayMap<>();
+ outFragment.getView().findNamedViews(outSharedElements);
+
+ final SharedElementCallback sharedElementCallback;
+ final ArrayList<String> names;
+ final BackStackRecord outTransaction = fragments.firstOutTransaction;
+ if (fragments.firstOutIsPop) {
+ sharedElementCallback = outFragment.getEnterTransitionCallback();
+ names = outTransaction.mSharedElementTargetNames;
+ } else {
+ sharedElementCallback = outFragment.getExitTransitionCallback();
+ names = outTransaction.mSharedElementSourceNames;
+ }
+
+ outSharedElements.retainAll(names);
+ if (sharedElementCallback != null) {
+ sharedElementCallback.onMapSharedElements(names, outSharedElements);
+ for (int i = names.size() - 1; i >= 0; i--) {
+ String name = names.get(i);
+ View view = outSharedElements.get(name);
+ if (view == null) {
+ nameOverrides.remove(name);
+ } else if (!name.equals(view.getTransitionName())) {
+ String targetValue = nameOverrides.remove(name);
+ nameOverrides.put(view.getTransitionName(), targetValue);
+ }
+ }
+ } else {
+ nameOverrides.retainAll(outSharedElements.keySet());
+ }
+ return outSharedElements;
+ }
+
+ /**
+ * Finds the shared elements in the incoming fragment. It also calls
+ * {@link SharedElementCallback#onMapSharedElements(List, Map)} to allow more control
+ * of the shared element mapping. {@code nameOverrides} is updated to match the
+ * actual transition name of the mapped shared elements.
+ *
+ * @param nameOverrides A map of the shared element names from the starting fragment to
+ * the final fragment's Views as given in
+ * {@link FragmentTransaction#addSharedElement(View, String)}.
+ * @param sharedElementTransition The shared element transition
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @return The mapping of shared element names to the Views in the hierarchy or null
+ * if there is no shared element transition.
+ */
+ private static ArrayMap<String, View> captureInSharedElements(
+ ArrayMap<String, String> nameOverrides, TransitionSet sharedElementTransition,
+ FragmentContainerTransition fragments) {
+ Fragment inFragment = fragments.lastIn;
+ final View fragmentView = inFragment.getView();
+ if (nameOverrides.isEmpty() || sharedElementTransition == null || fragmentView == null) {
+ nameOverrides.clear();
+ return null;
+ }
+ final ArrayMap<String, View> inSharedElements = new ArrayMap<>();
+ fragmentView.findNamedViews(inSharedElements);
+
+ final SharedElementCallback sharedElementCallback;
+ final ArrayList<String> names;
+ final BackStackRecord inTransaction = fragments.lastInTransaction;
+ if (fragments.lastInIsPop) {
+ sharedElementCallback = inFragment.getExitTransitionCallback();
+ names = inTransaction.mSharedElementSourceNames;
+ } else {
+ sharedElementCallback = inFragment.getEnterTransitionCallback();
+ names = inTransaction.mSharedElementTargetNames;
+ }
+
+ inSharedElements.retainAll(names);
+ if (sharedElementCallback != null) {
+ sharedElementCallback.onMapSharedElements(names, inSharedElements);
+ for (int i = names.size() - 1; i >= 0; i--) {
+ String name = names.get(i);
+ View view = inSharedElements.get(name);
+ if (view == null) {
+ String key = findKeyForValue(nameOverrides, name);
+ if (key != null) {
+ nameOverrides.remove(key);
+ }
+ } else if (!name.equals(view.getTransitionName())) {
+ String key = findKeyForValue(nameOverrides, name);
+ if (key != null) {
+ nameOverrides.put(key, view.getTransitionName());
+ }
+ }
+ }
+ } else {
+ retainValues(nameOverrides, inSharedElements);
+ }
+ return inSharedElements;
+ }
+
+ /**
+ * Utility to find the String key in {@code map} that maps to {@code value}.
+ */
+ private static String findKeyForValue(ArrayMap<String, String> map, String value) {
+ final int numElements = map.size();
+ for (int i = 0; i < numElements; i++) {
+ if (value.equals(map.valueAt(i))) {
+ return map.keyAt(i);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the View in the incoming Fragment that should be used as the epicenter.
+ *
+ * @param inSharedElements The mapping of shared element names to Views in the
+ * incoming fragment.
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @param enterTransition The transition used for the incoming Fragment's views
+ * @param inIsPop Is the incoming fragment being added as a pop transaction?
+ */
+ private static View getInEpicenterView(ArrayMap<String, View> inSharedElements,
+ FragmentContainerTransition fragments,
+ Transition enterTransition, boolean inIsPop) {
+ BackStackRecord inTransaction = fragments.lastInTransaction;
+ if (enterTransition != null && inTransaction.mSharedElementSourceNames != null &&
+ !inTransaction.mSharedElementSourceNames.isEmpty()) {
+ final String targetName = inIsPop
+ ? inTransaction.mSharedElementSourceNames.get(0)
+ : inTransaction.mSharedElementTargetNames.get(0);
+ return inSharedElements.get(targetName);
+ }
+ return null;
+ }
+
+ /**
+ * Sets the epicenter for the exit transition.
+ *
+ * @param sharedElementTransition The shared element transition
+ * @param exitTransition The transition for the outgoing fragment's views
+ * @param outSharedElements Shared elements in the outgoing fragment
+ * @param outIsPop Is the outgoing fragment being removed as a pop transaction?
+ * @param outTransaction The transaction that caused the fragment to be removed.
+ */
+ private static void setOutEpicenter(TransitionSet sharedElementTransition,
+ Transition exitTransition, ArrayMap<String, View> outSharedElements, boolean outIsPop,
+ BackStackRecord outTransaction) {
+ if (outTransaction.mSharedElementSourceNames != null &&
+ !outTransaction.mSharedElementSourceNames.isEmpty()) {
+ final String sourceName = outIsPop
+ ? outTransaction.mSharedElementTargetNames.get(0)
+ : outTransaction.mSharedElementSourceNames.get(0);
+ final View outEpicenterView = outSharedElements.get(sourceName);
+ setEpicenter(sharedElementTransition, outEpicenterView);
+
+ if (exitTransition != null) {
+ setEpicenter(exitTransition, outEpicenterView);
+ }
+ }
+ }
+
+ /**
+ * Sets a transition epicenter to the rectangle of a given View.
+ */
+ private static void setEpicenter(Transition transition, View view) {
+ if (view != null) {
+ final Rect epicenter = new Rect();
+ view.getBoundsOnScreen(epicenter);
+
+ transition.setEpicenterCallback(new Transition.EpicenterCallback() {
+ @Override
+ public Rect onGetEpicenter(Transition transition) {
+ return epicenter;
+ }
+ });
+ }
+ }
+
+ /**
+ * A utility to retain only the mappings in {@code nameOverrides} that have a value
+ * that has a key in {@code namedViews}. This is a useful equivalent to
+ * {@link ArrayMap#retainAll(Collection)} for values.
+ */
+ private static void retainValues(ArrayMap<String, String> nameOverrides,
+ ArrayMap<String, View> namedViews) {
+ for (int i = nameOverrides.size() - 1; i >= 0; i--) {
+ final String targetName = nameOverrides.valueAt(i);
+ if (!namedViews.containsKey(targetName)) {
+ nameOverrides.removeAt(i);
+ }
+ }
+ }
+
+ /**
+ * Calls the {@link SharedElementCallback#onSharedElementStart(List, List, List)} or
+ * {@link SharedElementCallback#onSharedElementEnd(List, List, List)} on the appropriate
+ * incoming or outgoing fragment.
+ *
+ * @param inFragment The incoming fragment
+ * @param outFragment The outgoing fragment
+ * @param isPop Is the incoming fragment part of a pop transaction?
+ * @param sharedElements The shared element Views
+ * @param isStart Call the start or end call on the SharedElementCallback
+ */
+ private static void callSharedElementStartEnd(Fragment inFragment, Fragment outFragment,
+ boolean isPop, ArrayMap<String, View> sharedElements, boolean isStart) {
+ SharedElementCallback sharedElementCallback = isPop
+ ? outFragment.getEnterTransitionCallback()
+ : inFragment.getEnterTransitionCallback();
+ if (sharedElementCallback != null) {
+ ArrayList<View> views = new ArrayList<>();
+ ArrayList<String> names = new ArrayList<>();
+ final int count = sharedElements == null ? 0 : sharedElements.size();
+ for (int i = 0; i < count; i++) {
+ names.add(sharedElements.keyAt(i));
+ views.add(sharedElements.valueAt(i));
+ }
+ if (isStart) {
+ sharedElementCallback.onSharedElementStart(names, views, null);
+ } else {
+ sharedElementCallback.onSharedElementEnd(names, views, null);
+ }
+ }
+ }
+
+ /**
+ * Finds all children of the shared elements and sets the wrapping TransitionSet
+ * targets to point to those. It also limits transitions that have no targets to the
+ * specific shared elements. This allows developers to target child views of the
+ * shared elements specifically, but this doesn't happen by default.
+ */
+ private static void setSharedElementTargets(TransitionSet transition,
+ View nonExistentView, ArrayList<View> sharedViews) {
+ final List<View> views = transition.getTargets();
+ views.clear();
+ final int count = sharedViews.size();
+ for (int i = 0; i < count; i++) {
+ final View view = sharedViews.get(i);
+ bfsAddViewChildren(views, view);
+ }
+ views.add(nonExistentView);
+ sharedViews.add(nonExistentView);
+ addTargets(transition, sharedViews);
+ }
+
+ /**
+ * Uses a breadth-first scheme to add startView and all of its children to views.
+ * It won't add a child if it is already in views.
+ */
+ private static void bfsAddViewChildren(final List<View> views, final View startView) {
+ final int startIndex = views.size();
+ if (containedBeforeIndex(views, startView, startIndex)) {
+ return; // This child is already in the list, so all its children are also.
+ }
+ views.add(startView);
+ for (int index = startIndex; index < views.size(); index++) {
+ final View view = views.get(index);
+ if (view instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) view;
+ final int childCount = viewGroup.getChildCount();
+ for (int childIndex = 0; childIndex < childCount; childIndex++) {
+ final View child = viewGroup.getChildAt(childIndex);
+ if (!containedBeforeIndex(views, child, startIndex)) {
+ views.add(child);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Does a linear search through views for view, limited to maxIndex.
+ */
+ private static boolean containedBeforeIndex(final List<View> views, final View view,
+ final int maxIndex) {
+ for (int i = 0; i < maxIndex; i++) {
+ if (views.get(i) == view) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * After the transition has started, remove all targets that we added to the transitions
+ * so that the transitions are left in a clean state.
+ */
+ private static void scheduleRemoveTargets(final Transition overalTransition,
+ final Transition enterTransition, final ArrayList<View> enteringViews,
+ final Transition exitTransition, final ArrayList<View> exitingViews,
+ final TransitionSet sharedElementTransition, final ArrayList<View> sharedElementsIn) {
+ overalTransition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionStart(Transition transition) {
+ if (enterTransition != null) {
+ replaceTargets(enterTransition, enteringViews, null);
+ }
+ if (exitTransition != null) {
+ replaceTargets(exitTransition, exitingViews, null);
+ }
+ if (sharedElementTransition != null) {
+ replaceTargets(sharedElementTransition, sharedElementsIn, null);
+ }
+ }
+ });
+ }
+
+ /**
+ * This method removes the views from transitions that target ONLY those views and
+ * replaces them with the new targets list.
+ * The views list should match those added in addTargets and should contain
+ * one view that is not in the view hierarchy (state.nonExistentView).
+ */
+ public static void replaceTargets(Transition transition, ArrayList<View> oldTargets,
+ ArrayList<View> newTargets) {
+ if (transition instanceof TransitionSet) {
+ TransitionSet set = (TransitionSet) transition;
+ int numTransitions = set.getTransitionCount();
+ for (int i = 0; i < numTransitions; i++) {
+ Transition child = set.getTransitionAt(i);
+ replaceTargets(child, oldTargets, newTargets);
+ }
+ } else if (!hasSimpleTarget(transition)) {
+ List<View> targets = transition.getTargets();
+ if (targets != null && targets.size() == oldTargets.size() &&
+ targets.containsAll(oldTargets)) {
+ // We have an exact match. We must have added these earlier in addTargets
+ final int targetCount = newTargets == null ? 0 : newTargets.size();
+ for (int i = 0; i < targetCount; i++) {
+ transition.addTarget(newTargets.get(i));
+ }
+ for (int i = oldTargets.size() - 1; i >= 0; i--) {
+ transition.removeTarget(oldTargets.get(i));
+ }
+ }
+ }
+ }
+
+ /**
+ * This method adds views as targets to the transition, but only if the transition
+ * doesn't already have a target. It is best for views to contain one View object
+ * that does not exist in the view hierarchy (state.nonExistentView) so that
+ * when they are removed later, a list match will suffice to remove the targets.
+ * Otherwise, if you happened to have targeted the exact views for the transition,
+ * the replaceTargets call will remove them unexpectedly.
+ */
+ public static void addTargets(Transition transition, ArrayList<View> views) {
+ if (transition == null) {
+ return;
+ }
+ if (transition instanceof TransitionSet) {
+ TransitionSet set = (TransitionSet) transition;
+ int numTransitions = set.getTransitionCount();
+ for (int i = 0; i < numTransitions; i++) {
+ Transition child = set.getTransitionAt(i);
+ addTargets(child, views);
+ }
+ } else if (!hasSimpleTarget(transition)) {
+ List<View> targets = transition.getTargets();
+ if (isNullOrEmpty(targets)) {
+ // We can just add the target views
+ int numViews = views.size();
+ for (int i = 0; i < numViews; i++) {
+ transition.addTarget(views.get(i));
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if there are any targets based on ID, transition or type.
+ */
+ private static boolean hasSimpleTarget(Transition transition) {
+ return !isNullOrEmpty(transition.getTargetIds()) ||
+ !isNullOrEmpty(transition.getTargetNames()) ||
+ !isNullOrEmpty(transition.getTargetTypes());
+ }
+
+ /**
+ * Simple utility to detect if a list is null or has no elements.
+ */
+ private static boolean isNullOrEmpty(List list) {
+ return list == null || list.isEmpty();
+ }
+
+ private static ArrayList<View> configureEnteringExitingViews(Transition transition,
+ Fragment fragment, ArrayList<View> sharedElements, View nonExistentView) {
+ ArrayList<View> viewList = null;
+ if (transition != null) {
+ viewList = new ArrayList<>();
+ View root = fragment.getView();
+ root.captureTransitioningViews(viewList);
+ if (sharedElements != null) {
+ viewList.removeAll(sharedElements);
+ }
+ if (!viewList.isEmpty()) {
+ viewList.add(nonExistentView);
+ addTargets(transition, viewList);
+ }
+ }
+ return viewList;
+ }
+
+ /**
+ * Sets the visibility of all Views in {@code views} to {@code visibility}.
+ */
+ private static void setViewVisibility(ArrayList<View> views, @View.Visibility int visibility) {
+ if (views == null) {
+ return;
+ }
+ for (int i = views.size() - 1; i >= 0; i--) {
+ final View view = views.get(i);
+ view.setVisibility(visibility);
+ }
+ }
+
+ /**
+ * Merges exit, shared element, and enter transitions so that they act together or
+ * sequentially as defined in the fragments.
+ */
+ private static Transition mergeTransitions(Transition enterTransition,
+ Transition exitTransition, Transition sharedElementTransition, Fragment inFragment,
+ boolean isPop) {
+ boolean overlap = true;
+ if (enterTransition != null && exitTransition != null && inFragment != null) {
+ overlap = isPop ? inFragment.getAllowReturnTransitionOverlap() :
+ inFragment.getAllowEnterTransitionOverlap();
+ }
+
+ // Wrap the transitions. Explicit targets like in enter and exit will cause the
+ // views to be targeted regardless of excluded views. If that happens, then the
+ // excluded fragments views (hidden fragments) will still be in the transition.
+
+ Transition transition;
+ if (overlap) {
+ // Regular transition -- do it all together
+ TransitionSet transitionSet = new TransitionSet();
+ if (enterTransition != null) {
+ transitionSet.addTransition(enterTransition);
+ }
+ if (exitTransition != null) {
+ transitionSet.addTransition(exitTransition);
+ }
+ if (sharedElementTransition != null) {
+ transitionSet.addTransition(sharedElementTransition);
+ }
+ transition = transitionSet;
+ } else {
+ // First do exit, then enter, but allow shared element transition to happen
+ // during both.
+ Transition staggered = null;
+ if (exitTransition != null && enterTransition != null) {
+ staggered = new TransitionSet()
+ .addTransition(exitTransition)
+ .addTransition(enterTransition)
+ .setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
+ } else if (exitTransition != null) {
+ staggered = exitTransition;
+ } else if (enterTransition != null) {
+ staggered = enterTransition;
+ }
+ if (sharedElementTransition != null) {
+ TransitionSet together = new TransitionSet();
+ if (staggered != null) {
+ together.addTransition(staggered);
+ }
+ together.addTransition(sharedElementTransition);
+ transition = together;
+ } else {
+ transition = staggered;
+ }
+ }
+ return transition;
+ }
+
+ /**
+ * Finds the first removed fragment and last added fragments when going forward.
+ * If none of the fragments have transitions, then both lists will be empty.
+ *
+ * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
+ * and last fragments to be added. This will be modified by
+ * this method.
+ */
+ public static void calculateFragments(BackStackRecord transaction,
+ SparseArray<FragmentContainerTransition> transitioningFragments,
+ boolean isOptimized) {
+ final int numOps = transaction.mOps.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ final BackStackRecord.Op op = transaction.mOps.get(opNum);
+ addToFirstInLastOut(transaction, op, transitioningFragments, false, isOptimized);
+ }
+ }
+
+ /**
+ * Finds the first removed fragment and last added fragments when popping the back stack.
+ * If none of the fragments have transitions, then both lists will be empty.
+ *
+ * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
+ * and last fragments to be added. This will be modified by
+ * this method.
+ */
+ public static void calculatePopFragments(BackStackRecord transaction,
+ SparseArray<FragmentContainerTransition> transitioningFragments, boolean isOptimized) {
+ if (!transaction.mManager.mContainer.onHasView()) {
+ return; // nothing to see, so no transitions
+ }
+ final int numOps = transaction.mOps.size();
+ for (int opNum = numOps - 1; opNum >= 0; opNum--) {
+ final BackStackRecord.Op op = transaction.mOps.get(opNum);
+ addToFirstInLastOut(transaction, op, transitioningFragments, true, isOptimized);
+ }
+ }
+
+ /**
+ * Examines the {@code command} and may set the first out or last in fragment for the fragment's
+ * container.
+ *
+ * @param transaction The executing transaction
+ * @param op The operation being run.
+ * @param transitioningFragments A structure holding the first in and last out fragments
+ * for each fragment container.
+ * @param isPop Is the operation a pop?
+ * @param isOptimizedTransaction True if the operations have been partially executed and the
+ * added fragments have Views in the hierarchy or false if the
+ * operations haven't been executed yet.
+ */
+ private static void addToFirstInLastOut(BackStackRecord transaction, BackStackRecord.Op op,
+ SparseArray<FragmentContainerTransition> transitioningFragments, boolean isPop,
+ boolean isOptimizedTransaction) {
+ final Fragment fragment = op.fragment;
+ final int containerId = fragment.mContainerId;
+ if (containerId == 0) {
+ return; // no container, no transition
+ }
+ final int command = isPop ? INVERSE_OPS[op.cmd] : op.cmd;
+ boolean setLastIn = false;
+ boolean wasRemoved = false;
+ boolean setFirstOut = false;
+ boolean wasAdded = false;
+ switch (command) {
+ case BackStackRecord.OP_SHOW:
+ if (isOptimizedTransaction) {
+ setLastIn = fragment.mHiddenChanged && !fragment.mHidden &&
+ fragment.mAdded;
+ } else {
+ setLastIn = fragment.mHidden;
+ }
+ wasAdded = true;
+ break;
+ case BackStackRecord.OP_ADD:
+ case BackStackRecord.OP_ATTACH:
+ if (isOptimizedTransaction) {
+ setLastIn = fragment.mIsNewlyAdded;
+ } else {
+ setLastIn = !fragment.mAdded && !fragment.mHidden;
+ }
+ wasAdded = true;
+ break;
+ case BackStackRecord.OP_HIDE:
+ if (isOptimizedTransaction) {
+ setFirstOut = fragment.mHiddenChanged && fragment.mAdded &&
+ fragment.mHidden;
+ } else {
+ setFirstOut = fragment.mAdded && !fragment.mHidden;
+ }
+ wasRemoved = true;
+ break;
+ case BackStackRecord.OP_REMOVE:
+ case BackStackRecord.OP_DETACH:
+ if (isOptimizedTransaction) {
+ setFirstOut = !fragment.mAdded && fragment.mView != null &&
+ fragment.mView.getVisibility() == View.VISIBLE;
+ } else {
+ setFirstOut = fragment.mAdded && !fragment.mHidden;
+ }
+ wasRemoved = true;
+ break;
+ }
+ FragmentContainerTransition containerTransition = transitioningFragments.get(containerId);
+ if (setLastIn) {
+ containerTransition =
+ ensureContainer(containerTransition, transitioningFragments, containerId);
+ containerTransition.lastIn = fragment;
+ containerTransition.lastInIsPop = isPop;
+ containerTransition.lastInTransaction = transaction;
+ }
+ if (!isOptimizedTransaction && wasAdded) {
+ if (containerTransition != null && containerTransition.firstOut == fragment) {
+ containerTransition.firstOut = null;
+ }
+
+ /**
+ * Ensure that fragments that are entering are at least at the CREATED state
+ * so that they may load Transitions using TransitionInflater.
+ */
+ FragmentManagerImpl manager = transaction.mManager;
+ if (fragment.mState < Fragment.CREATED && manager.mCurState >= Fragment.CREATED &&
+ manager.mHost.getContext().getApplicationInfo().targetSdkVersion >=
+ Build.VERSION_CODES.N && !transaction.mAllowOptimization) {
+ manager.makeActive(fragment);
+ manager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
+ }
+ }
+ if (setFirstOut && (containerTransition == null || containerTransition.firstOut == null)) {
+ containerTransition =
+ ensureContainer(containerTransition, transitioningFragments, containerId);
+ containerTransition.firstOut = fragment;
+ containerTransition.firstOutIsPop = isPop;
+ containerTransition.firstOutTransaction = transaction;
+ }
+
+ if (!isOptimizedTransaction && wasRemoved &&
+ (containerTransition != null && containerTransition.lastIn == fragment)) {
+ containerTransition.lastIn = null;
+ }
+ }
+
+ /**
+ * Ensures that a FragmentContainerTransition has been added to the SparseArray. If so,
+ * it returns the existing one. If not, one is created and added to the SparseArray and
+ * returned.
+ */
+ private static FragmentContainerTransition ensureContainer(
+ FragmentContainerTransition containerTransition,
+ SparseArray<FragmentContainerTransition> transitioningFragments, int containerId) {
+ if (containerTransition == null) {
+ containerTransition = new FragmentContainerTransition();
+ transitioningFragments.put(containerId, containerTransition);
+ }
+ return containerTransition;
+ }
+
+ /**
+ * Tracks the last fragment added and first fragment removed for fragment transitions.
+ * This also tracks which fragments are changed by push or pop transactions.
+ */
+ public static class FragmentContainerTransition {
+ /**
+ * The last fragment added/attached/shown in its container
+ */
+ public Fragment lastIn;
+
+ /**
+ * true when lastIn was added during a pop transaction or false if added with a push
+ */
+ public boolean lastInIsPop;
+
+ /**
+ * The transaction that included the last in fragment
+ */
+ public BackStackRecord lastInTransaction;
+
+ /**
+ * The first fragment with a View that was removed/detached/hidden in its container.
+ */
+ public Fragment firstOut;
+
+ /**
+ * true when firstOut was removed during a pop transaction or false otherwise
+ */
+ public boolean firstOutIsPop;
+
+ /**
+ * The transaction that included the first out fragment
+ */
+ public BackStackRecord firstOutTransaction;
+ }
+}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index c9d3682..5edd03f 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -274,12 +274,24 @@
/**
* Updates global configuration and applies changes to the entire system.
- * @param values Update values for global configuration.
+ * @param values Update values for global configuration. If null is passed it will request the
+ * Window Manager to compute new config for the default display.
* @throws RemoteException
* @return Returns true if the configuration was updated.
*/
public boolean updateConfiguration(Configuration values) throws RemoteException;
+ /**
+ * Updates override configuration applied to specific display.
+ * @param values Update values for display configuration. If null is passed it will request the
+ * Window Manager to compute new config for the specified display.
+ * @param displayId Id of the display to apply the config to.
+ * @throws RemoteException
+ * @return Returns true if the configuration was updated.
+ */
+ public boolean updateDisplayOverrideConfiguration(Configuration values, int displayId)
+ throws RemoteException;
+
public void setRequestedOrientation(IBinder token,
int requestedOrientation) throws RemoteException;
public int getRequestedOrientation(IBinder token) throws RemoteException;
@@ -647,6 +659,16 @@
public void enterPictureInPictureMode(IBinder token) throws RemoteException;
+ /**
+ * @return the default bounds of the PIP on the default display.
+ */
+ public Rect getDefaultPictureInPictureBounds() throws RemoteException;
+
+ /**
+ * @return the movement bounds of the PIP on the default display.
+ */
+ public Rect getPictureInPictureMovementBounds() throws RemoteException;
+
public int setVrMode(IBinder token, boolean enabled, ComponentName packageName)
throws RemoteException;
@@ -1091,4 +1113,7 @@
// Start of O transactions
int REQUEST_ACTIVITY_RELAUNCH = IBinder.FIRST_CALL_TRANSACTION+400;
+ int GET_DEFAULT_PICTURE_IN_PICTURE_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 401;
+ int GET_PICTURE_IN_PICTURE_MOVEMENT_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 402;
+ int UPDATE_DISPLAY_OVERRIDE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 403;
}
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
new file mode 100644
index 0000000..e2f6fb5
--- /dev/null
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2016 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 android.app;
+
+import android.app.IInstrumentationWatcher;
+import android.app.IUiAutomationConnection;
+import android.app.ProfilerInfo;
+import android.app.ResultInfo;
+import android.content.ComponentName;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * System private API for communicating with the application. This is given to
+ * the activity manager by an application when it starts up, for the activity
+ * manager to tell the application about things it needs to do.
+ *
+ * {@hide}
+ */
+oneway interface IApplicationThread {
+ /**
+ * Don't change the existing transaction Ids as they could be used in the native code.
+ * When adding a new method, assign the next available transaction id.
+ */
+ void schedulePauseActivity(IBinder token, boolean finished, boolean userLeaving,
+ int configChanges, boolean dontReport) = 1;
+ void scheduleStopActivity(IBinder token, boolean showWindow,
+ int configChanges) = 3;
+ void scheduleWindowVisibility(IBinder token, boolean showWindow) = 4;
+ void scheduleResumeActivity(IBinder token, int procState, boolean isForward,
+ in Bundle resumeArgs) = 5;
+ void scheduleSendResult(IBinder token, in List<ResultInfo> results) = 6;
+ void scheduleLaunchActivity(in Intent intent, IBinder token, int ident,
+ in ActivityInfo info, in Configuration curConfig, in Configuration overrideConfig,
+ in CompatibilityInfo compatInfo, in String referrer, IVoiceInteractor voiceInteractor,
+ int procState, in Bundle state, in PersistableBundle persistentState,
+ in List<ResultInfo> pendingResults, in List<ReferrerIntent> pendingNewIntents,
+ boolean notResumed, boolean isForward, in ProfilerInfo profilerInfo) = 7;
+ void scheduleNewIntent(
+ in List<ReferrerIntent> intent, IBinder token, boolean andPause) = 8;
+ void scheduleDestroyActivity(IBinder token, boolean finished,
+ int configChanges) = 9;
+ void scheduleReceiver(in Intent intent, in ActivityInfo info,
+ in CompatibilityInfo compatInfo,
+ int resultCode, in String data, in Bundle extras, boolean sync,
+ int sendingUser, int processState) = 10;
+ void scheduleCreateService(IBinder token, in ServiceInfo info,
+ in CompatibilityInfo compatInfo, int processState) = 11;
+ void scheduleStopService(IBinder token) = 12;
+ void bindApplication(in String packageName, in ApplicationInfo info,
+ in List<ProviderInfo> providers, in ComponentName testName,
+ in ProfilerInfo profilerInfo, in Bundle testArguments,
+ IInstrumentationWatcher testWatcher, IUiAutomationConnection uiAutomationConnection,
+ int debugMode, boolean enableBinderTracking, boolean trackAllocation,
+ boolean restrictedBackupMode, boolean persistent, in Configuration config,
+ in CompatibilityInfo compatInfo, in Map services,
+ in Bundle coreSettings, in String buildSerial) = 13;
+ void scheduleExit() = 14;
+ void scheduleConfigurationChanged(in Configuration config) = 16;
+ void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
+ int flags, in Intent args) = 17;
+ void updateTimeZone() = 18;
+ void processInBackground() = 19;
+ void scheduleBindService(IBinder token,
+ in Intent intent, boolean rebind, int processState) = 20;
+ void scheduleUnbindService(IBinder token,
+ in Intent intent) = 21;
+ void dumpService(in ParcelFileDescriptor fd, IBinder servicetoken,
+ in String[] args) = 22;
+ void scheduleRegisteredReceiver(IIntentReceiver receiver, in Intent intent,
+ int resultCode, in String data, in Bundle extras, boolean ordered,
+ boolean sticky, int sendingUser, int processState) = 23;
+ void scheduleLowMemory() = 24;
+ void scheduleActivityConfigurationChanged(IBinder token, in Configuration overrideConfig,
+ boolean reportToActivity) = 25;
+ void scheduleRelaunchActivity(IBinder token, in List<ResultInfo> pendingResults,
+ in List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed,
+ in Configuration config, in Configuration overrideConfig, boolean preserveWindow) = 26;
+ void scheduleSleeping(IBinder token, boolean sleeping) = 27;
+ void profilerControl(boolean start, in ProfilerInfo profilerInfo, int profileType) = 28;
+ void setSchedulingGroup(int group) = 29;
+ void scheduleCreateBackupAgent(in ApplicationInfo app, in CompatibilityInfo compatInfo,
+ int backupMode) = 30;
+ void scheduleDestroyBackupAgent(in ApplicationInfo app,
+ in CompatibilityInfo compatInfo) = 31;
+ void scheduleOnNewActivityOptions(IBinder token, in Bundle options) = 32;
+ void scheduleSuicide() = 33;
+ void dispatchPackageBroadcast(int cmd, in String[] packages) = 34;
+ void scheduleCrash(in String msg) = 35;
+ void dumpHeap(boolean managed, in String path, in ParcelFileDescriptor fd) = 36;
+ void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix,
+ in String[] args) = 37;
+ void clearDnsCache() = 38;
+ void setHttpProxy(in String proxy, in String port, in String exclList,
+ in Uri pacFileUrl) = 39;
+ void setCoreSettings(in Bundle coreSettings) = 40;
+ void updatePackageCompatibilityInfo(in String pkg, in CompatibilityInfo info) = 41;
+ void scheduleTrimMemory(int level) = 42;
+ void dumpMemInfo(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem, boolean checkin,
+ boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable,
+ in String[] args) = 43;
+ void dumpGfxInfo(in ParcelFileDescriptor fd, in String[] args) = 44;
+ void dumpProvider(in ParcelFileDescriptor fd, IBinder servicetoken,
+ in String[] args) = 45;
+ void dumpDbInfo(in ParcelFileDescriptor fd, in String[] args) = 46;
+ void unstableProviderDied(IBinder provider) = 47;
+ void requestAssistContextExtras(IBinder activityToken, IBinder requestToken,
+ int requestType, int sessionId) = 48;
+ void scheduleTranslucentConversionComplete(IBinder token, boolean timeout) = 49;
+ void setProcessState(int state) = 50;
+ void scheduleInstallProvider(in ProviderInfo provider) = 51;
+ void updateTimePrefs(boolean is24Hour) = 52;
+ void scheduleCancelVisibleBehind(IBinder token) = 53;
+ void scheduleBackgroundVisibleBehindChanged(IBinder token, boolean enabled) = 54;
+ void scheduleEnterAnimationComplete(IBinder token) = 55;
+ void notifyCleartextNetwork(in byte[] firstPacket) = 56;
+ void startBinderTracking() = 57;
+ void stopBinderTrackingAndDump(in ParcelFileDescriptor fd) = 58;
+ void scheduleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode) = 59;
+ void schedulePictureInPictureModeChanged(IBinder token,
+ boolean isInPictureInPictureMode) = 60;
+ void scheduleLocalVoiceInteractionStarted(IBinder token,
+ IVoiceInteractor voiceInteractor) = 61;
+ void handleTrustStorageUpdate() = 62;
+ void attachAgent(String path) = 63;
+ /**
+ * Don't change the existing transaction Ids as they could be used in the native code.
+ * When adding a new method, assign the next available transaction id.
+ */
+}
\ No newline at end of file
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
deleted file mode 100644
index 4189dd9..0000000
--- a/core/java/android/app/IApplicationThread.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2006 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 android.app;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.IIntentReceiver;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.ProviderInfo;
-import android.content.pm.ServiceInfo;
-import android.content.res.CompatibilityInfo;
-import android.content.res.Configuration;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Debug;
-import android.os.ParcelFileDescriptor;
-import android.os.PersistableBundle;
-import android.os.RemoteException;
-import android.os.IBinder;
-import android.os.IInterface;
-import com.android.internal.app.IVoiceInteractor;
-import com.android.internal.content.ReferrerIntent;
-
-import java.io.FileDescriptor;
-import java.util.List;
-import java.util.Map;
-
-/**
- * System private API for communicating with the application. This is given to
- * the activity manager by an application when it starts up, for the activity
- * manager to tell the application about things it needs to do.
- *
- * {@hide}
- */
-public interface IApplicationThread extends IInterface {
- void schedulePauseActivity(IBinder token, boolean finished, boolean userLeaving,
- int configChanges, boolean dontReport) throws RemoteException;
- void scheduleStopActivity(IBinder token, boolean showWindow,
- int configChanges) throws RemoteException;
- void scheduleWindowVisibility(IBinder token, boolean showWindow) throws RemoteException;
- void scheduleSleeping(IBinder token, boolean sleeping) throws RemoteException;
- void scheduleResumeActivity(IBinder token, int procState, boolean isForward, Bundle resumeArgs)
- throws RemoteException;
- void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException;
- void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
- CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
- int procState, Bundle state, PersistableBundle persistentState,
- List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) throws RemoteException;
- void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults,
- List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed,
- Configuration config, Configuration overrideConfig, boolean preserveWindow)
- throws RemoteException;
- void scheduleNewIntent(
- List<ReferrerIntent> intent, IBinder token, boolean andPause) throws RemoteException;
- void scheduleDestroyActivity(IBinder token, boolean finished,
- int configChanges) throws RemoteException;
- void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo,
- int resultCode, String data, Bundle extras, boolean sync,
- int sendingUser, int processState) throws RemoteException;
- static final int BACKUP_MODE_INCREMENTAL = 0;
- static final int BACKUP_MODE_FULL = 1;
- static final int BACKUP_MODE_RESTORE = 2;
- static final int BACKUP_MODE_RESTORE_FULL = 3;
- void scheduleCreateBackupAgent(ApplicationInfo app, CompatibilityInfo compatInfo,
- int backupMode) throws RemoteException;
- void scheduleDestroyBackupAgent(ApplicationInfo app, CompatibilityInfo compatInfo)
- throws RemoteException;
- void scheduleCreateService(IBinder token, ServiceInfo info,
- CompatibilityInfo compatInfo, int processState) throws RemoteException;
- void scheduleBindService(IBinder token,
- Intent intent, boolean rebind, int processState) throws RemoteException;
- void scheduleUnbindService(IBinder token,
- Intent intent) throws RemoteException;
- void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
- int flags, Intent args) throws RemoteException;
- void scheduleStopService(IBinder token) throws RemoteException;
- static final int DEBUG_OFF = 0;
- static final int DEBUG_ON = 1;
- static final int DEBUG_WAIT = 2;
- void bindApplication(String packageName, ApplicationInfo info, List<ProviderInfo> providers,
- ComponentName testName, ProfilerInfo profilerInfo, Bundle testArguments,
- IInstrumentationWatcher testWatcher, IUiAutomationConnection uiAutomationConnection,
- int debugMode, boolean enableBinderTracking, boolean trackAllocation,
- boolean restrictedBackupMode, boolean persistent, Configuration config,
- CompatibilityInfo compatInfo, Map<String, IBinder> services, Bundle coreSettings,
- String buildSerial) throws RemoteException;
- void scheduleExit() throws RemoteException;
- void scheduleSuicide() throws RemoteException;
- void scheduleConfigurationChanged(Configuration config) throws RemoteException;
- void updateTimeZone() throws RemoteException;
- void clearDnsCache() throws RemoteException;
- void setHttpProxy(String proxy, String port, String exclList,
- Uri pacFileUrl) throws RemoteException;
- void processInBackground() throws RemoteException;
- void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args)
- throws RemoteException;
- void dumpProvider(FileDescriptor fd, IBinder servicetoken, String[] args)
- throws RemoteException;
- void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
- int resultCode, String data, Bundle extras, boolean ordered,
- boolean sticky, int sendingUser, int processState) throws RemoteException;
- void scheduleLowMemory() throws RemoteException;
- void scheduleActivityConfigurationChanged(IBinder token, Configuration overrideConfig,
- boolean reportToActivity) throws RemoteException;
- void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType)
- throws RemoteException;
- void dumpHeap(boolean managed, String path, ParcelFileDescriptor fd)
- throws RemoteException;
- void setSchedulingGroup(int group) throws RemoteException;
- // the package has been removed, clean up internal references
- static final int PACKAGE_REMOVED = 0;
- static final int EXTERNAL_STORAGE_UNAVAILABLE = 1;
- // the package is being modified in-place, don't kill it and retain references to it
- static final int PACKAGE_REMOVED_DONT_KILL = 2;
- // a previously removed package was replaced with a new version [eg. upgrade, split added, ...]
- static final int PACKAGE_REPLACED = 3;
- void dispatchPackageBroadcast(int cmd, String[] packages) throws RemoteException;
- void scheduleCrash(String msg) throws RemoteException;
- void dumpActivity(FileDescriptor fd, IBinder servicetoken, String prefix, String[] args)
- throws RemoteException;
- void setCoreSettings(Bundle coreSettings) throws RemoteException;
- void updatePackageCompatibilityInfo(String pkg, CompatibilityInfo info) throws RemoteException;
- void scheduleTrimMemory(int level) throws RemoteException;
- void dumpMemInfo(FileDescriptor fd, Debug.MemoryInfo mem, boolean checkin, boolean dumpInfo,
- boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable,
- String[] args) throws RemoteException;
- void dumpGfxInfo(FileDescriptor fd, String[] args) throws RemoteException;
- void dumpDbInfo(FileDescriptor fd, String[] args) throws RemoteException;
- void unstableProviderDied(IBinder provider) throws RemoteException;
- void requestAssistContextExtras(IBinder activityToken, IBinder requestToken, int requestType,
- int sessionId) throws RemoteException;
- void scheduleTranslucentConversionComplete(IBinder token, boolean timeout)
- throws RemoteException;
- void scheduleOnNewActivityOptions(IBinder token, ActivityOptions options)
- throws RemoteException;
- void setProcessState(int state) throws RemoteException;
- void scheduleInstallProvider(ProviderInfo provider) throws RemoteException;
- void updateTimePrefs(boolean is24Hour) throws RemoteException;
- void scheduleCancelVisibleBehind(IBinder token) throws RemoteException;
- void scheduleBackgroundVisibleBehindChanged(IBinder token, boolean enabled) throws RemoteException;
- void scheduleEnterAnimationComplete(IBinder token) throws RemoteException;
- void notifyCleartextNetwork(byte[] firstPacket) throws RemoteException;
- void startBinderTracking() throws RemoteException;
- void stopBinderTrackingAndDump(FileDescriptor fd) throws RemoteException;
- void scheduleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode) throws RemoteException;
- void schedulePictureInPictureModeChanged(IBinder token, boolean isInPictureInPictureMode) throws RemoteException;
- void scheduleLocalVoiceInteractionStarted(IBinder token, IVoiceInteractor voiceInteractor) throws RemoteException;
- void handleTrustStorageUpdate() throws RemoteException;
-
- String descriptor = "android.app.IApplicationThread";
-
- int SCHEDULE_PAUSE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
- int SCHEDULE_STOP_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2;
- int SCHEDULE_WINDOW_VISIBILITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
- int SCHEDULE_RESUME_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
- int SCHEDULE_SEND_RESULT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+5;
- int SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+6;
- int SCHEDULE_NEW_INTENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+7;
- int SCHEDULE_FINISH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+8;
- int SCHEDULE_RECEIVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+9;
- int SCHEDULE_CREATE_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+10;
- int SCHEDULE_STOP_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+11;
- int BIND_APPLICATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+12;
- int SCHEDULE_EXIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+13;
-
- int SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+15;
- int SCHEDULE_SERVICE_ARGS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+16;
- int UPDATE_TIME_ZONE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+17;
- int PROCESS_IN_BACKGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+18;
- int SCHEDULE_BIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+19;
- int SCHEDULE_UNBIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+20;
- int DUMP_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+21;
- int SCHEDULE_REGISTERED_RECEIVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+22;
- int SCHEDULE_LOW_MEMORY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+23;
- int SCHEDULE_ACTIVITY_CONFIGURATION_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+24;
- int SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+25;
- int SCHEDULE_SLEEPING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26;
- int PROFILER_CONTROL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+27;
- int SET_SCHEDULING_GROUP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+28;
- int SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29;
- int SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30;
- int SCHEDULE_ON_NEW_ACTIVITY_OPTIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+31;
- int SCHEDULE_SUICIDE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32;
- int DISPATCH_PACKAGE_BROADCAST_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+33;
- int SCHEDULE_CRASH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+34;
- int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+35;
- int DUMP_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+36;
- int CLEAR_DNS_CACHE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+37;
- int SET_HTTP_PROXY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+38;
- int SET_CORE_SETTINGS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+39;
- int UPDATE_PACKAGE_COMPATIBILITY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+40;
- int SCHEDULE_TRIM_MEMORY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+41;
- int DUMP_MEM_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+42;
- int DUMP_GFX_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+43;
- int DUMP_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+44;
- int DUMP_DB_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+45;
- int UNSTABLE_PROVIDER_DIED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+46;
- int REQUEST_ASSIST_CONTEXT_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+47;
- int SCHEDULE_TRANSLUCENT_CONVERSION_COMPLETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+48;
- int SET_PROCESS_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+49;
- int SCHEDULE_INSTALL_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+50;
- int UPDATE_TIME_PREFS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+51;
- int CANCEL_VISIBLE_BEHIND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+52;
- int BACKGROUND_VISIBLE_BEHIND_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+53;
- int ENTER_ANIMATION_COMPLETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+54;
- int NOTIFY_CLEARTEXT_NETWORK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+55;
- int START_BINDER_TRACKING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+56;
- int STOP_BINDER_TRACKING_AND_DUMP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+57;
- int SCHEDULE_MULTI_WINDOW_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+58;
- int SCHEDULE_PICTURE_IN_PICTURE_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+59;
- int SCHEDULE_LOCAL_VOICE_INTERACTION_STARTED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+60;
- int HANDLE_TRUST_STORAGE_UPDATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+61;
-}
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 530b8bb..4150172 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -134,11 +134,11 @@
this.mLockscreenVisibility = lockscreenVisibility;
}
- // Modifiable by apps.
+ // Modifiable by apps on channel creation.
/**
* Sets the ringtone that should be played for notifications posted to this channel if
- * the notifications don't supply a ringtone.
+ * the notifications don't supply a ringtone. Only modifiable on channel creation.
*/
public void setDefaultRingtone(Uri defaultRingtone) {
this.mRingtone = defaultRingtone;
@@ -146,7 +146,7 @@
/**
* Sets whether notifications posted to this channel should display notification lights,
- * on devices that support that feature.
+ * on devices that support that feature. Only modifiable on channel creation.
*/
public void setLights(boolean lights) {
this.mLights = lights;
@@ -154,7 +154,7 @@
/**
* Sets whether notification posted to this channel should vibrate, even if individual
- * notifications are marked as having vibration.
+ * notifications are marked as having vibration only modifiable on channel creation.
*/
public void setVibration(boolean vibration) {
this.mVibration = vibration;
diff --git a/core/java/android/app/ProfilerInfo.aidl b/core/java/android/app/ProfilerInfo.aidl
new file mode 100644
index 0000000..dc744b9
--- /dev/null
+++ b/core/java/android/app/ProfilerInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2016 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 android.app;
+
+/** @hide */
+parcelable ProfilerInfo;
\ No newline at end of file
diff --git a/core/java/android/app/ResultInfo.aidl b/core/java/android/app/ResultInfo.aidl
new file mode 100644
index 0000000..6c12e1c
--- /dev/null
+++ b/core/java/android/app/ResultInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2016 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 android.app;
+
+/** @hide */
+parcelable ResultInfo;
\ No newline at end of file
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4ddcfe5..9ce9dec 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -26,6 +26,7 @@
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.app.Activity;
+import android.app.admin.PasswordMetrics;
import android.app.admin.SecurityLog.SecurityEvent;
import android.content.ComponentName;
import android.content.Context;
@@ -3523,12 +3524,10 @@
/**
* @hide
*/
- public void setActivePasswordState(int quality, int length, int letters, int uppercase,
- int lowercase, int numbers, int symbols, int nonletter, int userHandle) {
+ public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
if (mService != null) {
try {
- mService.setActivePasswordState(quality, length, letters, uppercase, lowercase,
- numbers, symbols, nonletter, userHandle);
+ mService.setActivePasswordState(metrics, userHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8c376bb..22219d7 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -18,6 +18,7 @@
package android.app.admin;
import android.app.admin.SystemUpdatePolicy;
+import android.app.admin.PasswordMetrics;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -29,6 +30,7 @@
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.UserHandle;
+
import java.util.List;
/**
@@ -117,8 +119,7 @@
void forceRemoveActiveAdmin(in ComponentName policyReceiver, int userHandle);
boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle);
- void setActivePasswordState(int quality, int length, int letters, int uppercase, int lowercase,
- int numbers, int symbols, int nonletter, int userHandle);
+ void setActivePasswordState(in PasswordMetrics metrics, int userHandle);
void reportFailedPasswordAttempt(int userHandle);
void reportSuccessfulPasswordAttempt(int userHandle);
void reportFailedFingerprintAttempt(int userHandle);
diff --git a/core/java/android/app/admin/PasswordMetrics.aidl b/core/java/android/app/admin/PasswordMetrics.aidl
new file mode 100644
index 0000000..90d7c69
--- /dev/null
+++ b/core/java/android/app/admin/PasswordMetrics.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2016, 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 android.app.admin;
+
+parcelable PasswordMetrics;
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
new file mode 100644
index 0000000..ea3f560
--- /dev/null
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2016 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 android.app.admin;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager;
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.io.IOException;
+
+/**
+ * A class that represents the metrics of a password that are used to decide whether or not a
+ * password meets the requirements.
+ *
+ * {@hide}
+ */
+public class PasswordMetrics implements Parcelable {
+ // Maximum allowed number of repeated or ordered characters in a sequence before we'll
+ // consider it a complex PIN/password.
+ public static final int MAX_ALLOWED_SEQUENCE = 3;
+
+ public int quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ public int length = 0;
+ public int letters = 0;
+ public int upperCase = 0;
+ public int lowerCase = 0;
+ public int numeric = 0;
+ public int symbols = 0;
+ public int nonLetter = 0;
+
+ public PasswordMetrics() {}
+
+ public PasswordMetrics(int quality, int length) {
+ this.quality = quality;
+ this.length = length;
+ }
+
+ public PasswordMetrics(int quality, int length, int letters, int upperCase, int lowerCase,
+ int numeric, int symbols, int nonLetter) {
+ this(quality, length);
+ this.letters = letters;
+ this.upperCase = upperCase;
+ this.lowerCase = lowerCase;
+ this.numeric = numeric;
+ this.symbols = symbols;
+ this.nonLetter = nonLetter;
+ }
+
+ private PasswordMetrics(Parcel in) {
+ quality = in.readInt();
+ length = in.readInt();
+ letters = in.readInt();
+ upperCase = in.readInt();
+ lowerCase = in.readInt();
+ numeric = in.readInt();
+ symbols = in.readInt();
+ nonLetter = in.readInt();
+ }
+
+ public boolean isDefault() {
+ return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
+ && length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0
+ && numeric == 0 && symbols == 0 && nonLetter == 0;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(quality);
+ dest.writeInt(length);
+ dest.writeInt(letters);
+ dest.writeInt(upperCase);
+ dest.writeInt(lowerCase);
+ dest.writeInt(numeric);
+ dest.writeInt(symbols);
+ dest.writeInt(nonLetter);
+ }
+
+ public static final Parcelable.Creator<PasswordMetrics> CREATOR
+ = new Parcelable.Creator<PasswordMetrics>() {
+ public PasswordMetrics createFromParcel(Parcel in) {
+ return new PasswordMetrics(in);
+ }
+
+ public PasswordMetrics[] newArray(int size) {
+ return new PasswordMetrics[size];
+ }
+ };
+
+ public static PasswordMetrics computeForPassword(@NonNull String password) {
+ // Analyse the characters used
+ int letters = 0;
+ int upperCase = 0;
+ int lowerCase = 0;
+ int numeric = 0;
+ int symbols = 0;
+ int nonLetter = 0;
+ final int length = password.length();
+ for (int i = 0; i < length; i++) {
+ switch (categoryChar(password.charAt(i))) {
+ case CHAR_LOWER_CASE:
+ letters++;
+ lowerCase++;
+ break;
+ case CHAR_UPPER_CASE:
+ letters++;
+ upperCase++;
+ break;
+ case CHAR_DIGIT:
+ numeric++;
+ nonLetter++;
+ break;
+ case CHAR_SYMBOL:
+ symbols++;
+ nonLetter++;
+ break;
+ }
+ }
+
+ // Determine the quality of the password
+ final boolean hasNumeric = numeric > 0;
+ final boolean hasNonNumeric = (letters + symbols) > 0;
+ final int quality;
+ if (hasNonNumeric && hasNumeric) {
+ quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+ } else if (hasNonNumeric) {
+ quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+ } else if (hasNumeric) {
+ quality = maxLengthSequence(password) > MAX_ALLOWED_SEQUENCE
+ ? DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ : DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+ } else {
+ quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ }
+
+ return new PasswordMetrics(
+ quality, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter);
+ }
+
+ /*
+ * Returns the maximum length of a sequential characters. A sequence is defined as
+ * monotonically increasing characters with a constant interval or the same character repeated.
+ *
+ * For example:
+ * maxLengthSequence("1234") == 4
+ * maxLengthSequence("13579") == 5
+ * maxLengthSequence("1234abc") == 4
+ * maxLengthSequence("aabc") == 3
+ * maxLengthSequence("qwertyuio") == 1
+ * maxLengthSequence("@ABC") == 3
+ * maxLengthSequence(";;;;") == 4 (anything that repeats)
+ * maxLengthSequence(":;<=>") == 1 (ordered, but not composed of alphas or digits)
+ *
+ * @param string the pass
+ * @return the number of sequential letters or digits
+ */
+ public static int maxLengthSequence(@NonNull String string) {
+ if (string.length() == 0) return 0;
+ char previousChar = string.charAt(0);
+ @CharacterCatagory int category = categoryChar(previousChar); //current sequence category
+ int diff = 0; //difference between two consecutive characters
+ boolean hasDiff = false; //if we are currently targeting a sequence
+ int maxLength = 0; //maximum length of a sequence already found
+ int startSequence = 0; //where the current sequence started
+ for (int current = 1; current < string.length(); current++) {
+ char currentChar = string.charAt(current);
+ @CharacterCatagory int categoryCurrent = categoryChar(currentChar);
+ int currentDiff = (int) currentChar - (int) previousChar;
+ if (categoryCurrent != category || Math.abs(currentDiff) > maxDiffCategory(category)) {
+ maxLength = Math.max(maxLength, current - startSequence);
+ startSequence = current;
+ hasDiff = false;
+ category = categoryCurrent;
+ }
+ else {
+ if(hasDiff && currentDiff != diff) {
+ maxLength = Math.max(maxLength, current - startSequence);
+ startSequence = current - 1;
+ }
+ diff = currentDiff;
+ hasDiff = true;
+ }
+ previousChar = currentChar;
+ }
+ maxLength = Math.max(maxLength, string.length() - startSequence);
+ return maxLength;
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({CHAR_UPPER_CASE, CHAR_LOWER_CASE, CHAR_DIGIT, CHAR_SYMBOL})
+ private @interface CharacterCatagory {}
+ private static final int CHAR_LOWER_CASE = 0;
+ private static final int CHAR_UPPER_CASE = 1;
+ private static final int CHAR_DIGIT = 2;
+ private static final int CHAR_SYMBOL = 3;
+
+ @CharacterCatagory
+ private static int categoryChar(char c) {
+ if ('a' <= c && c <= 'z') return CHAR_LOWER_CASE;
+ if ('A' <= c && c <= 'Z') return CHAR_UPPER_CASE;
+ if ('0' <= c && c <= '9') return CHAR_DIGIT;
+ return CHAR_SYMBOL;
+ }
+
+ private static int maxDiffCategory(@CharacterCatagory int category) {
+ switch (category) {
+ case CHAR_LOWER_CASE:
+ case CHAR_UPPER_CASE:
+ return 1;
+ case CHAR_DIGIT:
+ return 10;
+ default:
+ return 0;
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 38788dc..111085d 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -2060,7 +2060,7 @@
final private IBluetoothManagerCallback mManagerCallback =
new IBluetoothManagerCallback.Stub() {
public void onBluetoothServiceUp(IBluetooth bluetoothService) {
- if (VDBG) Log.d(TAG, "onBluetoothServiceUp: " + bluetoothService);
+ if (DBG) Log.d(TAG, "onBluetoothServiceUp: " + bluetoothService);
mServiceLock.writeLock().lock();
mService = bluetoothService;
@@ -2082,7 +2082,7 @@
}
public void onBluetoothServiceDown() {
- if (VDBG) Log.d(TAG, "onBluetoothServiceDown: " + mService);
+ if (DBG) Log.d(TAG, "onBluetoothServiceDown: " + mService);
try {
mServiceLock.writeLock().lock();
@@ -2110,7 +2110,7 @@
}
public void onBrEdrDown() {
- if (VDBG) Log.i(TAG, "on QBrEdrDown: ");
+ if (DBG) Log.i(TAG, "onBrEdrDown:");
}
};
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 2bb9012..0763149 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -44,14 +44,18 @@
private IBluetoothGatt mService;
private BluetoothGattCallback mCallback;
private int mClientIf;
- private boolean mAuthRetry = false;
private BluetoothDevice mDevice;
private boolean mAutoConnect;
+ private int mAuthRetryState;
private int mConnState;
private final Object mStateLock = new Object();
private Boolean mDeviceBusy = false;
private int mTransport;
+ private static final int AUTH_RETRY_STATE_IDLE = 0;
+ private static final int AUTH_RETRY_STATE_NO_MITM = 1;
+ private static final int AUTH_RETRY_STATE_MITM = 2;
+
private static final int CONN_STATE_IDLE = 0;
private static final int CONN_STATE_CONNECTING = 1;
private static final int CONN_STATE_CONNECTED = 2;
@@ -259,17 +263,19 @@
if ((status == GATT_INSUFFICIENT_AUTHENTICATION
|| status == GATT_INSUFFICIENT_ENCRYPTION)
- && mAuthRetry == false) {
+ && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
try {
- mAuthRetry = true;
- mService.readCharacteristic(mClientIf, address, handle, AUTHENTICATION_MITM);
+ final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) ?
+ AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
+ mService.readCharacteristic(mClientIf, address, handle, authReq);
+ mAuthRetryState++;
return;
} catch (RemoteException e) {
Log.e(TAG,"",e);
}
}
- mAuthRetry = false;
+ mAuthRetryState = AUTH_RETRY_STATE_IDLE;
BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, handle);
if (characteristic == null) {
@@ -308,19 +314,20 @@
if ((status == GATT_INSUFFICIENT_AUTHENTICATION
|| status == GATT_INSUFFICIENT_ENCRYPTION)
- && mAuthRetry == false) {
+ && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
try {
- mAuthRetry = true;
+ final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) ?
+ AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
mService.writeCharacteristic(mClientIf, address, handle,
- characteristic.getWriteType(), AUTHENTICATION_MITM,
- characteristic.getValue());
+ characteristic.getWriteType(), authReq, characteristic.getValue());
+ mAuthRetryState++;
return;
} catch (RemoteException e) {
Log.e(TAG,"",e);
}
}
- mAuthRetry = false;
+ mAuthRetryState = AUTH_RETRY_STATE_IDLE;
try {
mCallback.onCharacteristicWrite(BluetoothGatt.this, characteristic, status);
@@ -375,17 +382,19 @@
if ((status == GATT_INSUFFICIENT_AUTHENTICATION
|| status == GATT_INSUFFICIENT_ENCRYPTION)
- && mAuthRetry == false) {
+ && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
try {
- mAuthRetry = true;
- mService.readDescriptor(mClientIf, address, handle, AUTHENTICATION_MITM);
+ final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) ?
+ AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
+ mService.readDescriptor(mClientIf, address, handle, authReq);
+ mAuthRetryState++;
return;
} catch (RemoteException e) {
Log.e(TAG,"",e);
}
}
- mAuthRetry = true;
+ mAuthRetryState = AUTH_RETRY_STATE_IDLE;
try {
mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
@@ -414,18 +423,20 @@
if ((status == GATT_INSUFFICIENT_AUTHENTICATION
|| status == GATT_INSUFFICIENT_ENCRYPTION)
- && mAuthRetry == false) {
+ && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
try {
- mAuthRetry = true;
+ final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) ?
+ AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
mService.writeDescriptor(mClientIf, address, handle,
- AUTHENTICATION_MITM, descriptor.getValue());
+ authReq, descriptor.getValue());
+ mAuthRetryState++;
return;
} catch (RemoteException e) {
Log.e(TAG,"",e);
}
}
- mAuthRetry = false;
+ mAuthRetryState = AUTH_RETRY_STATE_IDLE;
try {
mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
@@ -499,6 +510,7 @@
mServices = new ArrayList<BluetoothGattService>();
mConnState = CONN_STATE_IDLE;
+ mAuthRetryState = AUTH_RETRY_STATE_IDLE;
}
/**
@@ -512,6 +524,7 @@
unregisterApp();
mConnState = CONN_STATE_CLOSED;
+ mAuthRetryState = AUTH_RETRY_STATE_IDLE;
}
/**
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 2ded4c8..243579a 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -275,6 +275,48 @@
}
/**
+ * Parse UUID to bytes. The returned value is shortest representation, a 16-bit, 32-bit or 128-bit UUID,
+ * Note returned value is little endian (Bluetooth).
+ *
+ * @param uuid uuid to parse.
+ * @return shortest representation of {@code uuid} as bytes.
+ * @throws IllegalArgumentException If the {@code uuid} is null.
+ */
+ public static byte[] uuidToBytes(ParcelUuid uuid) {
+ if (uuid == null) {
+ throw new IllegalArgumentException("uuid cannot be null");
+ }
+
+ if (is16BitUuid(uuid)) {
+ byte[] uuidBytes = new byte[UUID_BYTES_16_BIT];
+ int uuidVal = getServiceIdentifierFromParcelUuid(uuid);
+ uuidBytes[0] = (byte)(uuidVal & 0xFF);
+ uuidBytes[1] = (byte)((uuidVal & 0xFF00) >> 8);
+ return uuidBytes;
+ }
+
+ if (is32BitUuid(uuid)) {
+ byte[] uuidBytes = new byte[UUID_BYTES_32_BIT];
+ int uuidVal = getServiceIdentifierFromParcelUuid(uuid);
+ uuidBytes[0] = (byte)(uuidVal & 0xFF);
+ uuidBytes[1] = (byte)((uuidVal & 0xFF00) >> 8);
+ uuidBytes[2] = (byte)((uuidVal & 0xFF0000) >> 16);
+ uuidBytes[3] = (byte)((uuidVal & 0xFF000000) >> 24);
+ return uuidBytes;
+ }
+
+ // Construct a 128 bit UUID.
+ long msb = uuid.getUuid().getMostSignificantBits();
+ long lsb = uuid.getUuid().getLeastSignificantBits();
+
+ byte[] uuidBytes = new byte[UUID_BYTES_128_BIT];
+ ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN);
+ buf.putLong(8, msb);
+ buf.putLong(0, lsb);
+ return uuidBytes;
+ }
+
+ /**
* Check whether the given parcelUuid can be converted to 16 bit bluetooth uuid.
*
* @param parcelUuid
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index ed0ac53..a854b89 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -46,7 +46,7 @@
import java.util.Set;
/**
- * Represents a "launcher shortcut" that can be published via {@link ShortcutManager}.
+ * Represents a shortcut that can be published via {@link ShortcutManager}.
*
* @see ShortcutManager
*/
@@ -776,17 +776,17 @@
* activity is published using
* {@link ShortcutManager#addDynamicShortcuts(List)} or
* {@link ShortcutManager#setDynamicShortcuts(List)},
- * the first main activity defined in the application's <code>AndroidManifest.xml</code>
+ * the first main activity defined in the app's <code>AndroidManifest.xml</code>
* file is used.
*
* <li>Only "main" activities—ones that define the {@link Intent#ACTION_MAIN}
* and {@link Intent#CATEGORY_LAUNCHER} intent filters—can be target
* activities.
*
- * <li>By default, the first main activity defined in the application manifest is
+ * <li>By default, the first main activity defined in the app's manifest is
* the target activity.
*
- * <li>A target activity must belong to the publisher application.
+ * <li>A target activity must belong to the publisher app.
* </ul>
*
* @see ShortcutInfo#getActivity()
@@ -802,7 +802,7 @@
*
* <p>Icons are not available on {@link ShortcutInfo} instances
* returned by {@link ShortcutManager} or {@link LauncherApps}. The default launcher
- * application can use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}
+ * app can use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}
* or {@link LauncherApps#getShortcutBadgedIconDrawable(ShortcutInfo, int)} to fetch
* shortcut icons.
*
@@ -933,8 +933,8 @@
}
/**
- * Sets categories for a shortcut. Launcher applications may use this information to
- * categorise shortcuts.
+ * Sets categories for a shortcut. Launcher apps may use this information to
+ * categorize shortcuts.
*
* @see #SHORTCUT_CATEGORY_CONVERSATION
* @see ShortcutInfo#getCategories()
@@ -953,9 +953,9 @@
* {@link ShortcutManager#addDynamicShortcuts(List)} or
* {@link ShortcutManager#setDynamicShortcuts(List)}.
*
- * <p>A shortcut can launch any intent that the publisher application has permission to
+ * <p>A shortcut can launch any intent that the publisher app has permission to
* launch. For example, a shortcut can launch an unexported activity within the publisher
- * application. A shortcut intent doesn't have to point at the target activity.
+ * app. A shortcut intent doesn't have to point at the target activity.
*
* <p>The given {@code intent} can contain extras, but these extras must contain values
* of primitive types in order for the system to persist these values.
@@ -970,7 +970,9 @@
/**
* Sets multiple intents instead of a single intent, in order to launch an activity with
- * other activities in back stack. Use {@link TaskStackBuilder} to build intents.
+ * other activities in back stack. Use {@link TaskStackBuilder} to build intents. The
+ * last element in the list represents the only intent that doesn't place an activity on
+ * the back stack.
* See the {@link ShortcutManager} javadoc for details.
*
* @see Builder#setIntent(Intent)
@@ -1006,9 +1008,9 @@
}
/**
- * Extras that application can set for any purpose.
+ * Extras that the app can set for any purpose.
*
- * <p>Applications can store arbitrary shortcut metadata in extras and retrieve the
+ * <p>Apps can store arbitrary shortcut metadata in extras and retrieve the
* metadata later using {@link ShortcutInfo#getExtras()}.
*/
@NonNull
@@ -1029,7 +1031,7 @@
/**
* Returns the ID of a shortcut.
*
- * <p>Shortcut IDs are unique within each publisher application and must be stable across
+ * <p>Shortcut IDs are unique within each publisher app and must be stable across
* devices so that shortcuts will still be valid when restored on a different device.
* See {@link ShortcutManager} for details.
*/
@@ -1039,7 +1041,7 @@
}
/**
- * Return the package name of the publisher application.
+ * Return the package name of the publisher app.
*/
@NonNull
public String getPackage() {
@@ -1050,7 +1052,7 @@
* Return the target activity.
*
* <p>This has nothing to do with the activity that this shortcut will launch.
- * Launcher applications should show the launcher icon for the returned activity alongside
+ * Launcher apps should show the launcher icon for the returned activity alongside
* this shortcut.
*
* @see Builder#setActivity
@@ -1102,7 +1104,7 @@
}
/**
- * Return the shorter description of a shortcut.
+ * Return the short description of a shortcut.
*
* @see Builder#setShortLabel(CharSequence)
*/
@@ -1117,7 +1119,7 @@
}
/**
- * Return the longer description of a shortcut.
+ * Return the long description of a shortcut.
*
* @see Builder#setLongLabel(CharSequence)
*/
@@ -1161,7 +1163,7 @@
* Returns the intent that is executed when the user selects this shortcut.
* If setIntents() was used, then return the last intent in the array.
*
- * <p>Launcher applications <b>cannot</b> see the intent. If a {@link ShortcutInfo} is
+ * <p>Launcher apps <b>cannot</b> see the intent. If a {@link ShortcutInfo} is
* obtained via {@link LauncherApps}, then this method will always return null.
* Launchers can only start a shortcut intent with {@link LauncherApps#startShortcut}.
*
@@ -1180,7 +1182,7 @@
/**
* Return the intent set with {@link Builder#setIntents(Intent[])}.
*
- * <p>Launcher applications <b>cannot</b> see the intents. If a {@link ShortcutInfo} is
+ * <p>Launcher apps <b>cannot</b> see the intents. If a {@link ShortcutInfo} is
* obtained via {@link LauncherApps}, then this method will always return null.
* Launchers can only start a shortcut intent with {@link LauncherApps#startShortcut}.
*
@@ -1219,15 +1221,15 @@
/**
* "Rank" of a shortcut, which is a non-negative, sequential value that's unique for each
- * {@link #getActivity} for each of the two kinds, dynamic shortcuts and manifest shortcuts.
+ * {@link #getActivity} for each of the two types of shortcuts (static and dynamic).
*
- * <p>Because manifest shortcuts and dynamic shortcuts have overlapping ranks,
- * when a launcher application shows shortcuts for an activity, it should first show
- * the manifest shortcuts followed by the dynamic shortcuts. Within each of those categories,
+ * <p>Because static shortcuts and dynamic shortcuts have overlapping ranks,
+ * when a launcher app shows shortcuts for an activity, it should first show
+ * the static shortcuts, followed by the dynamic shortcuts. Within each of those categories,
* shortcuts should be sorted by rank in ascending order.
*
- * <p>"Floating" shortcuts (i.e. shortcuts that are neither dynamic nor manifest) will all
- * have rank 0, because there's no sorting for them.
+ * <p><em>Floating shortcuts</em>, or shortcuts that are neither static nor dynamic, will all
+ * have rank 0, because they aren't sorted.
*
* See the {@link ShortcutManager}'s class javadoc for details.
*
@@ -1274,7 +1276,7 @@
}
/**
- * Extras that application can set to any purposes.
+ * Extras that the app can set for any purpose.
*
* @see Builder#setExtras(PersistableBundle)
*/
@@ -1339,12 +1341,13 @@
}
/**
- * Return whether a shortcut is published from AndroidManifest.xml or not. If {@code true},
- * it's also {@link #isImmutable()}.
+ * Return whether a shortcut is static; that is, whether a shortcut is
+ * published from AndroidManifest.xml. If {@code true}, the shortcut is
+ * also {@link #isImmutable()}.
*
* <p>When an app is upgraded and a shortcut is no longer published from AndroidManifest.xml,
- * this will be set to {@code false}. If the shortcut is not pinned, then it'll just disappear.
- * However, if it's pinned, it will still be alive, and {@link #isEnabled()} will be
+ * this will be set to {@code false}. If the shortcut is not pinned, then it'll disappear.
+ * However, if it's pinned, it will still be visible, {@link #isEnabled()} will be
* {@code false} and {@link #isImmutable()} will be {@code true}.
*/
public boolean isDeclaredInManifest() {
@@ -1358,7 +1361,7 @@
}
/**
- * @return true if pinned but neither dynamic nor manifest.
+ * @return true if pinned but neither static nor dynamic.
* @hide
*/
public boolean isFloating() {
@@ -1374,9 +1377,10 @@
* Return if a shortcut is immutable, in which case it cannot be modified with any of
* {@link ShortcutManager} APIs.
*
- * <p>All manifest shortcuts are immutable. When a manifest shortcut is pinned and then
- * disabled because the app is upgraded and its AndroidManifest.xml no longer publishes it,
- * {@link #isDeclaredInManifest()} returns {@code false}, but it is still immutable.
+ * <p>All static shortcuts are immutable. When a static shortcut is pinned and is then
+ * disabled because it doesn't appear in AndroidManifest.xml for a newer version of the
+ * app, {@link #isDeclaredInManifest()} returns {@code false}, but the shortcut
+ * is still immutable.
*
* <p>All shortcuts originally published via the {@link ShortcutManager} APIs
* are all mutable.
@@ -1561,7 +1565,7 @@
}
/**
- * Replaces the intent
+ * Replaces the intent.
*
* @throws IllegalArgumentException when extra is not compatible with {@link PersistableBundle}.
*
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 96ad67c..a93870e 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -31,87 +31,90 @@
import java.util.List;
/**
- * The ShortcutManager manages "launcher shortcuts" (or simply "shortcuts"). Shortcuts provide
- * users
- * with quick access to activities other than an application's main activity in the currently-active
+ * The ShortcutManager manages an app's <em>shortcuts</em>. Shortcuts provide users
+ * with quick access to activities other than an app's main activity in the currently-active
* launcher. For example,
- * an email application may publish the "compose new email" action, which will directly open the
+ * an email app may publish the "compose new email" action, which will directly open the
* compose activity. The {@link ShortcutInfo} class contains information about each of the
* shortcuts themselves.
*
- * <h3>Dynamic Shortcuts and Manifest Shortcuts</h3>
+ * <h3>Static Shortcuts and Dynamic Shortcuts</h3>
*
- * There are two ways to publish shortcuts: manifest shortcuts and dynamic shortcuts.
+ * <p>
+ * There are two ways to publish shortcuts: static shortcuts and dynamic shortcuts.
*
* <ul>
- * <li>Manifest shortcuts are declared in a resource
- * XML, which is referenced in the publisher application's <code>AndroidManifest.xml</code> file.
- * Manifest shortcuts are published when an application is installed,
- * and the details of these shortcuts change when an application is upgraded with an updated XML
+ * <li>Static shortcuts are declared in a resource
+ * XML file, which is referenced in the publisher app's <code>AndroidManifest.xml</code> file.
+ * Static shortcuts are published when an app is installed,
+ * and the details of these shortcuts change when an app is upgraded with an updated XML
* file.
- * Manifest shortcuts are immutable, and their
+ * Static shortcuts are immutable, and their
* definitions, such as icons and labels, cannot be changed dynamically without upgrading the
- * publisher application.
+ * publisher app.
*
- * <li>Dynamic shortcuts are published at runtime using the {@link ShortcutManager} APIs.
- * Applications can publish, update, and remove dynamic shortcuts at runtime.
+ * <li>Dynamic shortcuts are published at runtime using this class's APIs.
+ * Apps can publish, update, and remove dynamic shortcuts at runtime.
* </ul>
*
- * <p>Only "main" activities—activities that handle the {@code MAIN} action and the
+ * <p>Only main activities—activities that handle the {@code MAIN} action and the
* {@code LAUNCHER} category—can have shortcuts.
- * If an application has multiple main activities, these activities will have different sets
+ * If an app has multiple main activities, these activities have different sets
* of shortcuts.
*
- * <p>Dynamic shortcuts and manifest shortcuts are shown in the currently active launcher when
- * the user long-presses on an application launcher icon. The actual gesture may be different
- * depending on the launcher application.
+ * <p>Static shortcuts and dynamic shortcuts are shown in the currently active launcher when
+ * the user long-presses on an app's launcher icon.
+ *
+ * <p class="note"><strong>Note: </strong>The actual gesture may be different
+ * depending on the launcher app.
*
* <p>Each launcher icon can have at most {@link #getMaxShortcutCountPerActivity()} number of
- * dynamic and manifest shortcuts combined.
+ * static and dynamic shortcuts combined.
*
*
* <h3>Pinning Shortcuts</h3>
*
- * Launcher applications allow users to "pin" shortcuts so they're easier to access. Both manifest
+ * <p>
+ * Launcher apps allow users to <em>pin</em> shortcuts so they're easier to access. Both static
* and dynamic shortcuts can be pinned.
* Pinned shortcuts <b>cannot</b> be removed by publisher
- * applications; they're removed only when the user removes them,
- * when the publisher application is uninstalled, or when the
- * user performs the "clear data" action on the publisher application from the device's Settings
- * application.
+ * apps; they're removed only when the user removes them,
+ * when the publisher app is uninstalled, or when the
+ * user performs the <strong>clear data</strong> action on the publisher app from the device's Settings
+ * app.
*
- * <p>However, the publisher application can <em>disable</em> pinned shortcuts so they cannot be
+ * <p>However, the publisher app can <em>disable</em> pinned shortcuts so they cannot be
* started. See the following sections for details.
*
*
* <h3>Updating and Disabling Shortcuts</h3>
*
* <p>When a dynamic shortcut is pinned, even when the publisher removes it as a dynamic shortcut,
- * the pinned shortcut will still be visible and launchable. This allows an application to have
+ * the pinned shortcut will still be visible and launchable. This allows an app to have
* more than {@link #getMaxShortcutCountPerActivity()} number of shortcuts.
*
* <p>For example, suppose {@link #getMaxShortcutCountPerActivity()} is 5:
- * <ul>
- * <li>A chat application publishes 5 dynamic shortcuts for the 5 most recent
- * conversations, "c1" - "c5".
+ * <ol>
+ * <li>A chat app publishes 5 dynamic shortcuts for the 5 most recent
+ * conversations (c1, c2, ..., c5).
*
* <li>The user pins all 5 of the shortcuts.
*
- * <li>Later, the user has started 3 additional conversations ("c6", "c7", and "c8"),
- * so the publisher application
+ * <li>Later, the user has started 3 additional conversations (c6, c7, and c8),
+ * so the publisher app
* re-publishes its dynamic shortcuts. The new dynamic shortcut list is:
- * "c4", "c5", "c6", "c7", and "c8".
- * The publisher application has to remove "c1", "c2", and "c3" because it can't have more than
+ * c4, c5, ..., c8.
+ * The publisher app has to remove c1, c2, and c3 because it can't have more than
* 5 dynamic shortcuts.
*
- * <li>However, even though "c1", "c2" and "c3" are no longer dynamic shortcuts, the pinned
+ * <li>However, even though c1, c2, and c3 are no longer dynamic shortcuts, the pinned
* shortcuts for these conversations are still available and launchable.
*
* <li>At this point, the user can access a total of 8 shortcuts that link to activities in
- * the publisher application, including the 3 pinned
- * shortcuts, even though it's allowed to have at most 5 dynamic shortcuts.
+ * the publisher app, including the 3 pinned
+ * shortcuts, even though an app can have at most 5 dynamic shortcuts.
*
- * <li>The application can use {@link #updateShortcuts(List)} to update any of the existing
+ * <li>The app can use {@link #updateShortcuts(List)} to update <em>any</em> of the existing
* 8 shortcuts, when, for example, the chat peers' icons have changed.
* </ul>
* The {@link #addDynamicShortcuts(List)} and {@link #setDynamicShortcuts(List)} methods
@@ -121,104 +124,108 @@
* lists of shortcuts to dynamic shortcuts.
*
*
- * <h4>Disabling Manifest Shortcuts</h4>
- * When an application is upgraded and the new version
- * no longer uses a manifest shortcut that appeared in the previous version, this deprecated
- * shortcut will no longer be published as a manifest shortcut.
+ * <h4>Disabling Static Shortcuts</h4>
+ * When an app is upgraded and the new version
+ * no longer uses a static shortcut that appeared in the previous version, this deprecated
+ * shortcut will no longer be published as a static shortcut.
*
* <p>If the deprecated shortcut is pinned, then the pinned shortcut will remain on the launcher,
* but it will be disabled automatically.
- * Note that, in this case, the pinned shortcut is no longer a manifest shortcut, but it's
- * still <b>immutable</b> and cannot be updated using the {@link ShortcutManager} APIs.
+ * Note that, in this case, the pinned shortcut is no longer a static shortcut, but it's
+ * still <b>immutable</b>. Therefore, it cannot be updated using this class's APIs.
*
*
* <h4>Disabling Dynamic Shortcuts</h4>
* Sometimes pinned shortcuts become obsolete and may not be usable. For example, a pinned shortcut
- * to a group chat will be unusable when the associated group chat is deleted. In cases like this,
- * applications should use {@link #disableShortcuts(List)}, which will remove the specified dynamic
- * shortcuts and also make any specified pinned shortcuts un-launchable.
+ * to a group chat becomes unusable when the associated group chat is deleted. In cases like this,
+ * apps should use {@link #disableShortcuts(List)}, which removes the specified dynamic
+ * shortcuts and also makes any specified pinned shortcuts un-launchable.
* The {@link #disableShortcuts(List, CharSequence)} method can also be used to disabled shortcuts
* and show users a custom error message when they attempt to launch the disabled shortcuts.
*
*
- * <h3>Publishing Manifest Shortcuts</h3>
+ * <h3>Publishing Static Shortcuts</h3>
*
- * In order to add manifest shortcuts to your application, first add
+ * <p>
+ * In order to add static shortcuts to your app, first add
* {@code <meta-data android:name="android.app.shortcuts" />} to your main activity in
* AndroidManifest.xml:
* <pre>
- * <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- * package="com.example.myapplication">
- * <application . . .>
- * <activity android:name="Main">
- * <intent-filter>
- * <action android:name="android.intent.action.MAIN" />
- * <category android:name="android.intent.category.LAUNCHER" />
- * </intent-filter>
- * <b><meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/></b>
- * </activity>
- * </application>
- * </manifest>
+ *<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ * package="com.example.myapplication">
+ * <application ... >
+ * <activity android:name="Main">
+ * <intent-filter>
+ * <action android:name="android.intent.action.MAIN" />
+ * <category android:name="android.intent.category.LAUNCHER" />
+ * </intent-filter>
+ * <strong><meta-data android:name="android.app.shortcuts"
+ * android:resource="@xml/shortcuts" /></strong>
+ * </activity>
+ * </application>
+ *</manifest>
* </pre>
*
- * Then, define your application's manifest shortcuts in the <code>res/xml/shortcuts.xml</code>
+ * Then, define your app's static shortcuts in the <code>res/xml/shortcuts.xml</code>
* file:
* <pre>
- * <shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
- * <shortcut
- * android:shortcutId="compose"
- * android:enabled="true"
- * android:icon="@drawable/compose_icon"
- * android:shortcutShortLabel="@string/compose_shortcut_short_label1"
- * android:shortcutLongLabel="@string/compose_shortcut_long_label1"
- * android:shortcutDisabledMessage="@string/compose_disabled_message1"
- * >
- * <intent
- * android:action="android.intent.action.VIEW"
- * android:targetPackage="com.example.myapplication"
- * android:targetClass="com.example.myapplication.ComposeActivity" />
- * <!-- more intents can go here; see below -->
- * <categories android:name="android.shortcut.conversation" />
- * </shortcut>
- * <!-- more shortcuts can go here -->
- * </shortcuts>
+ *<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
+ * <shortcut
+ * android:shortcutId="compose"
+ * android:enabled="true"
+ * android:icon="@drawable/compose_icon"
+ * android:shortcutShortLabel="@string/compose_shortcut_short_label1"
+ * android:shortcutLongLabel="@string/compose_shortcut_long_label1"
+ * android:shortcutDisabledMessage="@string/compose_disabled_message1">
+ * <intent
+ * android:action="android.intent.action.VIEW"
+ * android:targetPackage="com.example.myapplication"
+ * android:targetClass="com.example.myapplication.ComposeActivity" />
+ * <!-- If your shortcut is associated with multiple intents, include them
+ * here. The last intent in the list is what the user sees when they
+ * launch this shortcut. -->
+ * <categories android:name="android.shortcut.conversation" />
+ * </shortcut>
+ * <!-- Specify more shortcuts here. -->
+ *</shortcuts>
* </pre>
*
- * The following list includes descriptions for the different attributes within a manifest shortcut:
+ * The following list includes descriptions for the different attributes within a static shortcut:
* <dl>
- * <dt>android:shortcutId</dt>
+ * <dt>{@code android:shortcutId}</dt>
* <dd>Mandatory shortcut ID</dd>
*
- * <dt>android:enabled</dt>
+ * <dt>{@code android:enabled}</dt>
* <dd>Default is {@code true}. Can be set to {@code false} in order
- * to disable a manifest shortcut that was published in a previous version and and set a custom
- * disabled message. If a custom disabled message is not needed, then a manifest shortcut can
+ * to disable a static shortcut that was published in a previous version and set a custom
+ * disabled message. If a custom disabled message is not needed, then a static shortcut can
* be simply removed from the XML file rather than keeping it with {@code enabled="false"}.</dd>
*
- * <dt>android:icon</dt>
+ * <dt>{@code android:icon}</dt>
* <dd>Shortcut icon.</dd>
*
- * <dt>android:shortcutShortLabel</dt>
+ * <dt>{@code android:shortcutShortLabel}</dt>
* <dd>Mandatory shortcut short label.
* See {@link ShortcutInfo.Builder#setShortLabel(CharSequence)}.</dd>
*
- * <dt>android:shortcutLongLabel</dt>
+ * <dt>{@code android:shortcutLongLabel}</dt>
* <dd>Shortcut long label.
* See {@link ShortcutInfo.Builder#setLongLabel(CharSequence)}.</dd>
*
- * <dt>android:shortcutDisabledMessage</dt>
+ * <dt>{@code android:shortcutDisabledMessage}</dt>
* <dd>When {@code android:enabled} is set to
* {@code false}, this attribute is used to display a custom disabled message.</dd>
*
- * <dt>intent</dt>
+ * <dt>{@code intent}</dt>
* <dd>Intent to launch when the user selects the shortcut.
* {@code android:action} is mandatory.
* See <a href="{@docRoot}guide/topics/ui/settings.html#Intents">Using intents</a> for the
* other supported tags.
- * You can provide multiple intents for a single shortcut so that an activity is launched
- * with other activities in the back stack. See {@link android.app.TaskStackBuilder} for details.
+ * You can provide multiple intents for a single shortcut so that the last defined activity is launched
+ * with the other activities in the <a href="/guide/components/tasks-and-back-stack.html">back stack</a>.
+ * See {@link android.app.TaskStackBuilder} for details.
* </dd>
- * <dt>categories</dt>
+ * <dt>{@code categories}</dt>
* <dd>Specify shortcut categories. Currently only
* {@link ShortcutInfo#SHORTCUT_CATEGORY_CONVERSATION} is defined in the framework.
* </dd>
@@ -226,64 +233,68 @@
*
* <h3>Publishing Dynamic Shortcuts</h3>
*
- * Applications can publish dynamic shortcuts with {@link #setDynamicShortcuts(List)}
+ * <p>
+ * Apps can publish dynamic shortcuts with {@link #setDynamicShortcuts(List)}
* or {@link #addDynamicShortcuts(List)}. The {@link #updateShortcuts(List)} method can also be
* used to update existing, mutable shortcuts.
* Use {@link #removeDynamicShortcuts(List)} or {@link #removeAllDynamicShortcuts()} to remove
* dynamic shortcuts.
*
- * <p>Example:
+ * <p>The following code snippet shows how to create a single dynamic shortcut:
* <pre>
- * ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
+ *ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
*
- * ShortcutInfo shortcut = new ShortcutInfo.Builder(this, "id1")
- * .setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.mysite.com/")))
- * .setShortLabel("Web site")
- * .setLongLabel("Open the web site")
- * .setIcon(Icon.createWithResource(context, R.drawable.icon_website))
- * .build();
+ *ShortcutInfo shortcut = new ShortcutInfo.Builder(this, "id1")
+ * .setShortLabel("Web site")
+ * .setLongLabel("Open the web site")
+ * .setIcon(Icon.createWithResource(context, R.drawable.icon_website))
+ * .setIntent(new Intent(Intent.ACTION_VIEW,
+ * Uri.parse("https://www.mysite.example.com/")))
+ * .build();
*
- * shortcutManager.setDynamicShortcuts(Arrays.asList(shortcut));
+ *shortcutManager.setDynamicShortcuts(Arrays.asList(shortcut));
* </pre>
*
*
* <h3>Shortcut Intents</h3>
+ * <p>
* Dynamic shortcuts can be published with any set of {@link Intent#addFlags Intent} flags.
* Typically, {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} is specified, possibly along with other
- * flags; otherwise, if the application is already running, the application is simply brought to
+ * flags; otherwise, if the app is already running, the app is simply brought to
* the foreground, and the target activity may not appear.
*
* <p>The {@link ShortcutInfo.Builder#setIntents(Intent[])} method can be used instead of
* {@link ShortcutInfo.Builder#setIntent(Intent)} with {@link android.app.TaskStackBuilder}
* in order to launch an activity with other activities in the back stack.
* When the user selects a shortcut to load an activity with a back stack,
- * then presses the back key, a "parent" activity will be shown instead of the user being
- * navigated back to the launcher.
+ * then presses the back key, a parent activity from the same app will be shown
+ * instead of the user being navigated back to the launcher.
*
- * <p>Manifest shortcuts can also have multiple intents to achieve the same effect.
+ * <p>Static shortcuts can also have multiple intents to achieve the same effect.
* In order to associate multiple {@link Intent} objects with a shortcut, simply list multiple
* <code><intent></code> elements within a single <code><shortcut></code> element.
- * The last intent specifies what the user will see when they launch a shortcut.
+ * The last intent specifies what the user sees when they launch a shortcut.
*
- * <p>Manifest shortcuts <b>cannot</b> have custom intent flags.
- * The first intent of a manifest shortcut will always have {@link Intent#FLAG_ACTIVITY_NEW_TASK}
+ * <p>Static shortcuts <b>cannot</b> have custom intent flags.
+ * The first intent of a static shortcut will always have {@link Intent#FLAG_ACTIVITY_NEW_TASK}
* and {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} set.
- * This means, when the application is already running, all the existing activities will be
- * destroyed when a manifest shortcut is launched.
+ * This means, when the app is already running, all the existing activities will be
+ * destroyed when a static shortcut is launched.
* If this behavior is not desirable, you can use a <em>trampoline activity</em>,
* or an invisible activity that starts another activity in {@link Activity#onCreate},
* then calls {@link Activity#finish()}.
* The first activity should include an attribute setting
- * of {@code android:taskAffinity=""} in the application's <code>AndroidManifest.xml</code>
- * file, and the intent within the manifest shortcut should point at this first activity.
+ * of {@code android:taskAffinity=""} in the app's <code>AndroidManifest.xml</code>
+ * file, and the intent within the static shortcut should point at this first activity.
*
*
* <h3>Showing New Information in a Shortcut</h3>
+ * <p>
* In order to avoid confusion, you should not use {@link #updateShortcuts(List)} to update
* a shortcut so that it contains conceptually different information.
*
- * <p>For example, a phone application may publish the most frequently called contact as a dynamic
- * shortcut. Over time, this contact may change; when it does, the application should
+ * <p>For example, a phone app may publish the most frequently called contact as a dynamic
+ * shortcut. Over time, this contact may change. When it does, the app should
* represent the changed contact with a new shortcut that contains a different ID, using either
* {@link #setDynamicShortcuts(List)} or {@link #addDynamicShortcuts(List)}, rather than updating
* the existing shortcut with {@link #updateShortcuts(List)}.
@@ -291,7 +302,7 @@
* it to reference a different contact will likely confuse the user.
*
* <p>On the other hand, when the
- * contact's information has changed, such as the name or picture, the application should
+ * contact's information has changed, such as the name or picture, the app should
* use {@link #updateShortcuts(List)} so that the pinned shortcut is updated too.
*
*
@@ -299,21 +310,21 @@
* When the launcher displays the shortcuts that are associated with a particular launcher icon,
* the shortcuts should appear in the following order:
* <ul>
- * <li>First show manifest shortcuts
+ * <li>First show static shortcuts
* (if {@link ShortcutInfo#isDeclaredInManifest()} is {@code true}),
* and then show dynamic shortcuts (if {@link ShortcutInfo#isDynamic()} is {@code true}).
- * <li>Within each category of shortcuts (manifest and dynamic), sort the shortcuts in order
+ * <li>Within each category of shortcuts (static and dynamic), sort the shortcuts in order
* of increasing rank according to {@link ShortcutInfo#getRank()}.
* </ul>
- * <p>Shortcut ranks are non-negative sequential integers
+ * <p>Shortcut ranks are non-negative, sequential integers
* that determine the order in which shortcuts appear, assuming that the shortcuts are all in
* the same category.
* Ranks of existing shortcuts can be updated with
- * {@link #updateShortcuts(List)}; you can use {@link #addDynamicShortcuts(List)} and
- * {@link #setDynamicShortcuts(List)}, too.
+ * {@link #updateShortcuts(List)}. You can also use {@link #addDynamicShortcuts(List)} and
+ * {@link #setDynamicShortcuts(List)}.
*
* <p>Ranks are auto-adjusted so that they're unique for each target activity in each category
- * (dynamic or manifest). For example, if there are 3 dynamic shortcuts with ranks 0, 1 and 2,
+ * (static or dynamic). For example, if there are 3 dynamic shortcuts with ranks 0, 1 and 2,
* adding another dynamic shortcut with a rank of 1 represents a request to place this shortcut at
* the second position.
* In response, the third and fourth shortcuts move closer to the bottom of the shortcut list,
@@ -321,119 +332,120 @@
*
* <h3>Rate Limiting</h3>
*
+ * <p>
* Calls to {@link #setDynamicShortcuts(List)}, {@link #addDynamicShortcuts(List)}, and
- * {@link #updateShortcuts(List)} may be rate-limited when called by background applications, or
- * applications with no foreground activity or service. When you attempt to call these methods
- * from a background application after exceeding the rate limit, these APIs return {@code false}.
+ * {@link #updateShortcuts(List)} may be rate-limited when called by <em>background apps</em>, or
+ * apps with no foreground activity or service. When you attempt to call these methods
+ * from a background app after exceeding the rate limit, these APIs return {@code false}.
*
- * <p>Applications with a foreground activity or service are not rate-limited.
+ * <p>Apps with a foreground activity or service are not rate-limited.
*
- * <p>Rate-limiting will be reset upon certain events, so that even background applications
- * can call these APIs again until the rate limit is reached again.
+ * <p>Rate-limiting is reset upon certain events, so that even background apps
+ * can call these APIs until the rate limit is reached again.
* These events include the following:
* <ul>
- * <li>When an application comes to the foreground.
- * <li>When the system locale changes.
- * <li>When the user performs an "inline reply" action on a notification.
+ * <li>An app comes to the foreground.
+ * <li>The system locale changes.
+ * <li>The user performs the <strong>inline reply</strong> action on a notification.
* </ul>
*
* <p>When rate-limiting is active, {@link #isRateLimitingActive()} returns {@code true}.
*
* <h4>Resetting rate-limiting for testing</h4>
*
- * If your application is rate-limited during development or testing, you can use the
- * "Reset ShortcutManager rate-limiting" development option or the following adb command to reset
- * it:
- * <pre>
- * adb shell cmd shortcut reset-throttling [ --user USER-ID ]
+ * <p>
+ * If your app is rate-limited during development or testing, you can use the
+ * <strong>Reset ShortcutManager rate-limiting</strong> development option or
+ * the following {@code adb} command to reset it:
+ * <pre class="no-pretty-print">
+ *$ adb shell cmd shortcut reset-throttling [ --user USER-ID ]
* </pre>
*
* <h3>Handling System Locale Changes</h3>
*
- * Applications should update dynamic and pinned shortcuts when the system locale changes
+ * <p>
+ * Apps should update dynamic and pinned shortcuts when the system locale changes
* using the {@link Intent#ACTION_LOCALE_CHANGED} broadcast.
*
- * <p>When the system locale changes, rate-limiting is reset, so even background applications
- * can set dynamic shortcuts, add dynamic shortcuts, and update shortcuts until the rate limit
- * is reached again.
+ * <p>When the system locale changes, rate-limiting is reset, so even background apps
+ * can add and update dynamic shortcuts until the rate limit is reached again.
*
*
* <h3>Backup and Restore</h3>
*
- * When an application has the {@code android:allowBackup="true"} attribute assignment included
+ * <p>
+ * When an app has the {@code android:allowBackup="true"} attribute assignment included
* in its <code>AndroidManifest.xml</code> file, pinned shortcuts are
* backed up automatically and are restored when the user sets up a new device.
*
- * <h4>Categories of Shortcuts that are Backed Up</h4>
+ * <h4>Categories of shortcuts that are backed up</h4>
*
* <ul>
* <li>Pinned shortcuts are backed up. Bitmap icons are not backed up by the system,
- * but launcher applications should back them up and restore them so that the user still sees icons
- * for pinned shortcuts on the launcher. Applications can always use
+ * so launcher apps should back them up and restore them so that the user still sees icons
+ * for pinned shortcuts on the launcher. Apps can always use
* {@link #updateShortcuts(List)} to re-publish icons.
*
- * <li>Manifest shortcuts are not backed up, but when an application is re-installed on a new
- * device, they are re-published from the <code>AndroidManifest.xml</code> file, anyway.
+ * <li>Static shortcuts aren't backed up, but when an app is re-installed on a new
+ * device, they are re-published from the <code>AndroidManifest.xml</code> file.
*
- * <li>Dynamic shortcuts are <b>not</b> backed up.
+ * <li>Dynamic shortcuts <b>aren't</b> backed up.
* </ul>
*
- * <p>Because dynamic shortcuts are not restored, it is recommended that applications check
+ * <p>Because dynamic shortcuts are not restored, it is recommended that apps check
* currently-published dynamic shortcuts using {@link #getDynamicShortcuts()}
* each time they are launched, and they should re-publish
* dynamic shortcuts when necessary.
*
* <pre>
- * public class MainActivity extends Activity {
- * public void onCreate(Bundle savedInstanceState) {
- * super.onCreate(savedInstanceState);
+ *public class MainActivity extends Activity {
+ * public void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * ShortcutManager shortcutManager =
+ * getSystemService(ShortcutManager.class);
*
- * ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
- *
- * if (shortcutManager.getDynamicShortcuts().size() == 0) {
- * // Application restored; re-publish dynamic shortcuts.
- *
- * if (shortcutManager.getPinnedShortcuts().size() > 0) {
- * // Pinned shortcuts have been restored. Use updateShortcuts() to make sure
- * // they have up-to-date information.
- * }
- * }
- * }
- * :
- *
- * }
+ * if (shortcutManager.getDynamicShortcuts().size() == 0) {
+ * // Application restored. Need to re-publish dynamic shortcuts.
+ * if (shortcutManager.getPinnedShortcuts().size() > 0) {
+ * // Pinned shortcuts have been restored. Use
+ * // updateShortcuts() to make sure they contain
+ * // up-to-date information.
+ * }
+ * }
+ * }
+ * // ...
+ *}
* </pre>
*
*
* <h4>Backup/restore and shortcut IDs</h4>
- *
- * Because pinned shortcuts are backed up and restored on new devices, shortcut IDs should be
- * meaningful across devices; that is, IDs should contain either stable, constant strings
- * or server-side identifiers,
+ * <p>
+ * Because pinned shortcuts are backed up and restored on new devices, shortcut IDs
+ * should contain either stable, constant strings or server-side identifiers,
* rather than identifiers generated locally that might not make sense on other devices.
*
*
* <h3>Report Shortcut Usage and Prediction</h3>
- *
- * Launcher applications may be capable of predicting which shortcuts will most likely be
+ * <p>
+ * Launcher apps may be capable of predicting which shortcuts will most likely be
* used at a given time by examining the shortcut usage history data.
*
- * <p>In order to provide launchers with such data, publisher applications should
+ * <p>In order to provide launchers with such data, publisher apps should
* report the shortcuts that are used with {@link #reportShortcutUsed(String)}
* when a shortcut is selected,
* <b>or when an action equivalent to a shortcut is taken by the user even if it wasn't started
* with the shortcut</b>.
*
- * <p>For example, suppose a GPS navigation application supports "navigate to work" as a shortcut.
+ * <p>For example, suppose a navigation app supports "navigate to work" as a shortcut.
* It should then report when the user selects this shortcut <b>and</b> when the user chooses
- * to navigate to work within the application itself.
- * This helps the launcher application
+ * to navigate to work within the app itself.
+ * This helps the launcher app
* learn that the user wants to navigate to work at a certain time every
* weekday, and it can then show this shortcut in a suggestion list at the right time.
*
* <h3>Launcher API</h3>
*
- * The {@link LauncherApps} class provides APIs for launcher applications to access shortcuts.
+ * The {@link LauncherApps} class provides APIs for launcher apps to access shortcuts.
*
*
* <h3>Direct Boot and Shortcuts</h3>
@@ -465,7 +477,7 @@
}
/**
- * Publish the list of shortcuts. All existing dynamic shortcuts from the caller application
+ * Publish the list of shortcuts. All existing dynamic shortcuts from the caller app
* will be replaced. If there are already pinned shortcuts with the same IDs,
* the mutable pinned shortcuts are updated.
*
@@ -488,7 +500,7 @@
}
/**
- * Return all dynamic shortcuts from the caller application.
+ * Return all dynamic shortcuts from the caller app.
*
* @throws IllegalStateException when the user is locked.
*/
@@ -503,7 +515,7 @@
}
/**
- * Return all manifest shortcuts from the caller application.
+ * Return all static (manifest) shortcuts from the caller app.
*
* @throws IllegalStateException when the user is locked.
*/
@@ -554,7 +566,7 @@
}
/**
- * Delete all dynamic shortcuts from the caller application.
+ * Delete all dynamic shortcuts from the caller app.
*
* @throws IllegalStateException when the user is locked.
*/
@@ -567,7 +579,7 @@
}
/**
- * Return all pinned shortcuts from the caller application.
+ * Return all pinned shortcuts from the caller app.
*
* @throws IllegalStateException when the user is locked.
*/
@@ -661,7 +673,7 @@
/**
* Re-enable pinned shortcuts that were previously disabled. If the target shortcuts
- * already enabled, this method does nothing.
+ * are already enabled, this method does nothing.
*
* @throws IllegalArgumentException If trying to enable immutable shortcuts.
*
@@ -684,7 +696,7 @@
}
/**
- * Return the maximum number of dynamic and manifest shortcuts that each launcher icon
+ * Return the maximum number of static and dynamic shortcuts that each launcher icon
* can have at a time.
*/
public int getMaxShortcutCountPerActivity() {
@@ -697,7 +709,7 @@
}
/**
- * Return the number of times the caller application can call the rate-limited APIs
+ * Return the number of times the caller app can call the rate-limited APIs
* before the rate limit counter is reset.
*
* @see #getRateLimitResetTime()
@@ -729,7 +741,7 @@
}
/**
- * Return {@code true} when rate-limiting is active for the caller application.
+ * Return {@code true} when rate-limiting is active for the caller app.
*
* <p>See the class level javadoc for details.
*
@@ -769,13 +781,13 @@
}
/**
- * Applications that publish shortcuts should call this method
- * whenever the user selects the shortcut containing the given ID or when the user completes
- * an action in the application that is equivalent to selecting the shortcut.
+ * Apps that publish shortcuts should call this method whenever the user
+ * selects the shortcut containing the given ID or when the user completes
+ * an action in the app that is equivalent to selecting the shortcut.
* For more details, see the Javadoc for the {@link ShortcutManager} class
*
* <p>The information is accessible via {@link UsageStatsManager#queryEvents}
- * Typically, launcher applications use this information to build a prediction model
+ * Typically, launcher apps use this information to build a prediction model
* so that they can promote the shortcuts that are likely to be used at the moment.
*
* @throws IllegalStateException when the user is locked.
@@ -790,9 +802,9 @@
}
/**
- * Called internally when an application is considered to have come to foreground
+ * Called internally when an app is considered to have come to the foreground
* even when technically it's not. This method resets the throttling for this package.
- * For example, when the user sends an "inline reply" on an notification, the system UI will
+ * For example, when the user sends an "inline reply" on a notification, the system UI will
* call it.
*
* @hide
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index 1d85493..62b7f32 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -722,6 +722,36 @@
}
/**
+ * This method is called when camera device's input capture queue becomes empty,
+ * and is ready to accept the next request.
+ *
+ * <p>Pending capture requests exist in one of two queues: the in-flight queue where requests
+ * are already in different stages of processing pipeline, and an input queue where requests
+ * wait to enter the in-flight queue. The input queue is needed because more requests may be
+ * submitted than the current camera device pipeline depth.</p>
+ *
+ * <p>This callback is fired when the input queue becomes empty, and the camera device may
+ * have to fall back to the repeating request if set, or completely skip the next frame from
+ * the sensor. This can cause glitches to camera preview output, for example. This callback
+ * will only fire after requests queued by capture() or captureBurst(), not after a
+ * repeating request or burst enters the in-flight queue. For example, in the common case
+ * of a repeating request and a single-shot JPEG capture, this callback only fires when the
+ * JPEG request has entered the in-flight queue for capture.</p>
+ *
+ * <p>By only sending a new {@link #capture} or {@link #captureBurst} when the input
+ * queue is empty, pipeline latency can be minimized.</p>
+ *
+ * <p>This callback is not fired when the session is first created. It is different from
+ * {@link #onReady}, which is fired when all requests in both queues have been processed.</p>
+ *
+ * @param session
+ * The session returned by {@link CameraDevice#createCaptureSession}
+ */
+ public void onCaptureQueueEmpty(@NonNull CameraCaptureSession session) {
+ // default empty implementation
+ }
+
+ /**
* This method is called when the session is closed.
*
* <p>A session is closed when a new session is created by the parent camera device,
diff --git a/core/java/android/hardware/camera2/impl/CallbackProxies.java b/core/java/android/hardware/camera2/impl/CallbackProxies.java
index dac2ef8..e6e448e 100644
--- a/core/java/android/hardware/camera2/impl/CallbackProxies.java
+++ b/core/java/android/hardware/camera2/impl/CallbackProxies.java
@@ -173,6 +173,11 @@
}
@Override
+ public void onCaptureQueueEmpty(CameraCaptureSession session) {
+ mProxy.invoke("onCaptureQueueEmpty", session);
+ }
+
+ @Override
public void onClosed(CameraCaptureSession session) {
mProxy.invoke("onClosed", session);
}
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index b10c341..5e9fd66 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -604,11 +604,16 @@
}
@Override
- public void onSurfacePrepared(Surface surface) {
- if (DEBUG) Log.v(TAG, mIdString + "onPrepared");
- mStateCallback.onSurfacePrepared(session, surface);
+ public void onRequestQueueEmpty() {
+ if (DEBUG) Log.v(TAG, mIdString + "onRequestQueueEmpty");
+ mStateCallback.onCaptureQueueEmpty(session);
}
+ @Override
+ public void onSurfacePrepared(Surface surface) {
+ if (DEBUG) Log.v(TAG, mIdString + "onSurfacePrepared");
+ mStateCallback.onSurfacePrepared(session, surface);
+ }
};
}
diff --git a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
index 1c8e124..4481a74 100644
--- a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
@@ -291,6 +291,11 @@
}
@Override
+ public void onCaptureQueueEmpty(CameraCaptureSession session) {
+ mCallback.onCaptureQueueEmpty(CameraConstrainedHighSpeedCaptureSessionImpl.this);
+ }
+
+ @Override
public void onClosed(CameraCaptureSession session) {
mCallback.onClosed(CameraConstrainedHighSpeedCaptureSessionImpl.this);
}
@@ -300,7 +305,5 @@
mCallback.onSurfacePrepared(CameraConstrainedHighSpeedCaptureSessionImpl.this,
surface);
}
-
-
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index ee8a6d7..97ca56ef 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1204,6 +1204,14 @@
}
/**
+ * This method is called when camera device's non-repeating request queue is empty,
+ * and is ready to start capturing next image.
+ */
+ public void onRequestQueueEmpty() {
+ // Default empty implementation
+ }
+
+ /**
* The method called when the camera device has finished preparing
* an output Surface
*/
@@ -1906,6 +1914,23 @@
sessionCallback.onSurfacePrepared(surface);
}
+ @Override
+ public void onRequestQueueEmpty() {
+ final StateCallbackKK sessionCallback;
+
+ if (DEBUG) {
+ Log.v(TAG, "Request queue becomes empty");
+ }
+
+ synchronized(mInterfaceLock) {
+ sessionCallback = mSessionStateCallback;
+ }
+
+ if (sessionCallback == null) return;
+
+ sessionCallback.onRequestQueueEmpty();
+ }
+
/**
* Called by onDeviceError for handling single-capture failures.
*/
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index b9e75ee..2a9bf6b 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -206,6 +206,7 @@
private static final int RESULT_RECEIVED = 3;
private static final int PREPARED = 4;
private static final int REPEATING_REQUEST_ERROR = 5;
+ private static final int REQUEST_QUEUE_EMPTY = 6;
private final HandlerThread mHandlerThread;
private Handler mHandler;
@@ -262,7 +263,6 @@
getHandler().sendMessage(msg);
}
-
@Override
public void onRepeatingRequestError(long lastFrameNumber) {
Message msg = getHandler().obtainMessage(REPEATING_REQUEST_ERROR,
@@ -272,6 +272,13 @@
}
@Override
+ public void onRequestQueueEmpty() {
+ Message msg = getHandler().obtainMessage(REQUEST_QUEUE_EMPTY,
+ /* arg1 */ 0, /* arg2 */ 0);
+ getHandler().sendMessage(msg);
+ }
+
+ @Override
public IBinder asBinder() {
// This is solely intended to be used for in-process binding.
return null;
@@ -327,6 +334,10 @@
mCallbacks.onRepeatingRequestError(lastFrameNumber);
break;
}
+ case REQUEST_QUEUE_EMPTY: {
+ mCallbacks.onRequestQueueEmpty();
+ break;
+ }
default:
throw new IllegalArgumentException(
"Unknown callback message " + msg.what);
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index e0d3905..a05a8ec 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -248,7 +248,8 @@
return program;
}
- private void drawFrame(SurfaceTexture st, int width, int height, int flipType) {
+ private void drawFrame(SurfaceTexture st, int width, int height, int flipType)
+ throws LegacyExceptionUtils.BufferQueueAbandonedException {
checkGlError("onDrawFrame start");
st.getTransformMatrix(mSTMatrix);
@@ -343,7 +344,7 @@
/*offset*/ 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /*offset*/ 0, /*count*/ 4);
- checkGlError("glDrawArrays");
+ checkGlDrawError("glDrawArrays");
}
/**
@@ -548,7 +549,29 @@
private void checkGlError(String msg) {
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
- throw new IllegalStateException(msg + ": GLES20 error: 0x" + Integer.toHexString(error));
+ throw new IllegalStateException(
+ msg + ": GLES20 error: 0x" + Integer.toHexString(error));
+ }
+ }
+
+ private void checkGlDrawError(String msg)
+ throws LegacyExceptionUtils.BufferQueueAbandonedException {
+ int error;
+ boolean surfaceAbandoned = false;
+ boolean glError = false;
+ while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+ if (error == GLES20.GL_OUT_OF_MEMORY) {
+ surfaceAbandoned = true;
+ } else {
+ glError = true;
+ }
+ }
+ if (glError) {
+ throw new IllegalStateException(
+ msg + ": GLES20 error: 0x" + Integer.toHexString(error));
+ }
+ if (surfaceAbandoned) {
+ throw new LegacyExceptionUtils.BufferQueueAbandonedException();
}
}
@@ -759,9 +782,14 @@
if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
makeCurrent(holder.eglSurface);
// glReadPixels reads from the bottom of the buffer, so add an extra vertical flip
- drawFrame(mSurfaceTexture, holder.width, holder.height,
- (mFacing == CameraCharacteristics.LENS_FACING_FRONT) ?
- FLIP_TYPE_BOTH : FLIP_TYPE_VERTICAL);
+ try {
+ drawFrame(mSurfaceTexture, holder.width, holder.height,
+ (mFacing == CameraCharacteristics.LENS_FACING_FRONT) ?
+ FLIP_TYPE_BOTH : FLIP_TYPE_VERTICAL);
+ } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+ // Should never hit this.
+ throw new IllegalStateException("Surface abandoned, skipping drawFrame...", e);
+ }
mPBufferPixels.clear();
GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 9e5aaf5..0073dde 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2679,6 +2679,7 @@
public static final int CALLBACK_IP_CHANGED = BASE + 7;
/** @hide */
public static final int CALLBACK_RELEASED = BASE + 8;
+ // TODO: consider deleting CALLBACK_EXIT and shifting following enum codes down by 1.
/** @hide */
public static final int CALLBACK_EXIT = BASE + 9;
/** @hide obj = NetworkCapabilities, arg1 = seq number */
@@ -2709,24 +2710,17 @@
}
private class CallbackHandler extends Handler {
- private final HashMap<NetworkRequest, NetworkCallback>mCallbackMap;
- private final AtomicInteger mRefCount;
private static final String TAG = "ConnectivityManager.CallbackHandler";
- private final ConnectivityManager mCm;
private static final boolean DBG = false;
- CallbackHandler(Looper looper, HashMap<NetworkRequest, NetworkCallback>callbackMap,
- AtomicInteger refCount, ConnectivityManager cm) {
+ CallbackHandler(Looper looper) {
super(looper);
- mCallbackMap = callbackMap;
- mRefCount = refCount;
- mCm = cm;
}
@Override
public void handleMessage(Message message) {
- NetworkRequest request = (NetworkRequest) getObject(message, NetworkRequest.class);
- Network network = (Network) getObject(message, Network.class);
+ NetworkRequest request = getObject(message, NetworkRequest.class);
+ Network network = getObject(message, Network.class);
if (DBG) {
Log.d(TAG, whatToString(message.what) + " for network " + network);
}
@@ -2769,9 +2763,7 @@
case CALLBACK_CAP_CHANGED: {
NetworkCallback callback = getCallback(request, "CAP_CHANGED");
if (callback != null) {
- NetworkCapabilities cap = (NetworkCapabilities)getObject(message,
- NetworkCapabilities.class);
-
+ NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);
callback.onCapabilitiesChanged(network, cap);
}
break;
@@ -2779,9 +2771,7 @@
case CALLBACK_IP_CHANGED: {
NetworkCallback callback = getCallback(request, "IP_CHANGED");
if (callback != null) {
- LinkProperties lp = (LinkProperties)getObject(message,
- LinkProperties.class);
-
+ LinkProperties lp = getObject(message, LinkProperties.class);
callback.onLinkPropertiesChanged(network, lp);
}
break;
@@ -2801,24 +2791,16 @@
break;
}
case CALLBACK_RELEASED: {
- NetworkCallback callback = null;
- synchronized(mCallbackMap) {
- callback = mCallbackMap.remove(request);
+ final NetworkCallback callback;
+ synchronized(sCallbacks) {
+ callback = sCallbacks.remove(request);
}
- if (callback != null) {
- synchronized(mRefCount) {
- if (mRefCount.decrementAndGet() == 0) {
- getLooper().quit();
- }
- }
- } else {
+ if (callback == null) {
Log.e(TAG, "callback not found for RELEASED message");
}
break;
}
case CALLBACK_EXIT: {
- Log.d(TAG, "Listener quitting");
- getLooper().quit();
break;
}
case EXPIRE_LEGACY_REQUEST: {
@@ -2828,14 +2810,14 @@
}
}
- private Object getObject(Message msg, Class c) {
- return msg.getData().getParcelable(c.getSimpleName());
+ private <T> T getObject(Message msg, Class<T> c) {
+ return (T) msg.getData().getParcelable(c.getSimpleName());
}
private NetworkCallback getCallback(NetworkRequest req, String name) {
NetworkCallback callback;
- synchronized(mCallbackMap) {
- callback = mCallbackMap.get(req);
+ synchronized(sCallbacks) {
+ callback = sCallbacks.get(req);
}
if (callback == null) {
Log.e(TAG, "callback not found for " + name + " message");
@@ -2844,63 +2826,56 @@
}
}
- private void incCallbackHandlerRefCount() {
- synchronized(sCallbackRefCount) {
- if (sCallbackRefCount.incrementAndGet() == 1) {
- // TODO: switch this to ConnectivityThread
- HandlerThread callbackThread = new HandlerThread("ConnectivityManager");
- callbackThread.start();
- sCallbackHandler = new CallbackHandler(callbackThread.getLooper(),
- sNetworkCallback, sCallbackRefCount, this);
+ private CallbackHandler getHandler() {
+ synchronized (sCallbacks) {
+ if (sCallbackHandler == null) {
+ sCallbackHandler = new CallbackHandler(ConnectivityThread.getInstanceLooper());
}
+ return sCallbackHandler;
}
}
- private void decCallbackHandlerRefCount() {
- synchronized(sCallbackRefCount) {
- if (sCallbackRefCount.decrementAndGet() == 0) {
- sCallbackHandler.obtainMessage(CALLBACK_EXIT).sendToTarget();
- sCallbackHandler = null;
- }
- }
- }
-
- static final HashMap<NetworkRequest, NetworkCallback> sNetworkCallback =
- new HashMap<NetworkRequest, NetworkCallback>();
- static final AtomicInteger sCallbackRefCount = new AtomicInteger(0);
- static CallbackHandler sCallbackHandler = null;
+ static final HashMap<NetworkRequest, NetworkCallback> sCallbacks = new HashMap<>();
+ static CallbackHandler sCallbackHandler;
private final static int LISTEN = 1;
private final static int REQUEST = 2;
private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
- NetworkCallback networkCallback, int timeoutMs, int action,
- int legacyType) {
- if (networkCallback == null) {
+ NetworkCallback callback, int timeoutMs, int action, int legacyType) {
+ return sendRequestForNetwork(need, callback, getHandler(), timeoutMs, action, legacyType);
+ }
+
+ private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
+ NetworkCallback callback, Handler handler, int timeoutMs, int action, int legacyType) {
+ if (callback == null) {
throw new IllegalArgumentException("null NetworkCallback");
}
if (need == null && action != REQUEST) {
throw new IllegalArgumentException("null NetworkCapabilities");
}
+ // TODO: throw an exception if callback.networkRequest is not null.
+ // http://b/20701525
+ final NetworkRequest request;
try {
- incCallbackHandlerRefCount();
- synchronized(sNetworkCallback) {
+ synchronized(sCallbacks) {
+ Messenger messenger = new Messenger(handler);
+ Binder binder = new Binder();
if (action == LISTEN) {
- networkCallback.networkRequest = mService.listenForNetwork(need,
- new Messenger(sCallbackHandler), new Binder());
+ request = mService.listenForNetwork(need, messenger, binder);
} else {
- networkCallback.networkRequest = mService.requestNetwork(need,
- new Messenger(sCallbackHandler), timeoutMs, new Binder(), legacyType);
+ request = mService.requestNetwork(
+ need, messenger, timeoutMs, binder, legacyType);
}
- if (networkCallback.networkRequest != null) {
- sNetworkCallback.put(networkCallback.networkRequest, networkCallback);
+ if (request != null) {
+ sCallbacks.put(request, callback);
}
+ callback.networkRequest = request;
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- if (networkCallback.networkRequest == null) decCallbackHandlerRefCount();
- return networkCallback.networkRequest;
+ return request;
}
/**
diff --git a/core/java/android/net/ConnectivityThread.java b/core/java/android/net/ConnectivityThread.java
index 55c3402..0b218e7 100644
--- a/core/java/android/net/ConnectivityThread.java
+++ b/core/java/android/net/ConnectivityThread.java
@@ -27,25 +27,30 @@
* @hide
*/
public final class ConnectivityThread extends HandlerThread {
- private static ConnectivityThread sInstance;
+
+ // A class implementing the lazy holder idiom: the unique static instance
+ // of ConnectivityThread is instantiated in a thread-safe way (guaranteed by
+ // the language specs) the first time that Singleton is referenced in get()
+ // or getInstanceLooper().
+ private static class Singleton {
+ private static final ConnectivityThread INSTANCE = createInstance();
+ }
private ConnectivityThread() {
super("ConnectivityThread");
}
- private static synchronized ConnectivityThread getInstance() {
- if (sInstance == null) {
- sInstance = new ConnectivityThread();
- sInstance.start();
- }
- return sInstance;
+ private static ConnectivityThread createInstance() {
+ ConnectivityThread t = new ConnectivityThread();
+ t.start();
+ return t;
}
public static ConnectivityThread get() {
- return getInstance();
+ return Singleton.INSTANCE;
}
public static Looper getInstanceLooper() {
- return getInstance().getLooper();
+ return Singleton.INSTANCE.getLooper();
}
}
diff --git a/core/java/android/net/IIpConnectivityMetrics.aidl b/core/java/android/net/IIpConnectivityMetrics.aidl
index 8f634bb..d36b766 100644
--- a/core/java/android/net/IIpConnectivityMetrics.aidl
+++ b/core/java/android/net/IIpConnectivityMetrics.aidl
@@ -23,7 +23,8 @@
interface IIpConnectivityMetrics {
/**
- * @return number of remaining available slots in buffer.
+ * @return the number of remaining available slots in buffer,
+ * or -1 if the event was dropped due to rate limiting.
*/
int logEvent(in ConnectivityMetricsEvent event);
}
diff --git a/core/java/android/net/InterfaceConfiguration.java b/core/java/android/net/InterfaceConfiguration.java
index eace8b2..34cde08 100644
--- a/core/java/android/net/InterfaceConfiguration.java
+++ b/core/java/android/net/InterfaceConfiguration.java
@@ -113,7 +113,7 @@
*/
public boolean isActive() {
try {
- if (hasFlag(FLAG_UP)) {
+ if (isUp()) {
for (byte b : mAddr.getAddress().getAddress()) {
if (b != 0) return true;
}
diff --git a/core/java/android/os/Debug.aidl b/core/java/android/os/Debug.aidl
new file mode 100644
index 0000000..81a2c1f
--- /dev/null
+++ b/core/java/android/os/Debug.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 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 android.os;
+
+parcelable Debug.MemoryInfo;
\ No newline at end of file
diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java
index 180e8f4..c7612d1 100644
--- a/core/java/android/os/HwParcel.java
+++ b/core/java/android/os/HwParcel.java
@@ -16,6 +16,9 @@
package android.os;
+import java.util.ArrayList;
+import java.util.Arrays;
+
import libcore.util.NativeAllocationRegistry;
/** @hide */
@@ -53,14 +56,88 @@
public native final void writeDouble(double val);
public native final void writeString(String val);
- public native final void writeBoolVector(boolean[] val);
- public native final void writeInt8Vector(byte[] val);
- public native final void writeInt16Vector(short[] val);
- public native final void writeInt32Vector(int[] val);
- public native final void writeInt64Vector(long[] val);
- public native final void writeFloatVector(float[] val);
- public native final void writeDoubleVector(double[] val);
- public native final void writeStringVector(String[] val);
+ private native final void writeBoolVector(boolean[] val);
+ private native final void writeInt8Vector(byte[] val);
+ private native final void writeInt16Vector(short[] val);
+ private native final void writeInt32Vector(int[] val);
+ private native final void writeInt64Vector(long[] val);
+ private native final void writeFloatVector(float[] val);
+ private native final void writeDoubleVector(double[] val);
+ private native final void writeStringVector(String[] val);
+
+ public final void writeBoolVector(ArrayList<Boolean> val) {
+ final int n = val.size();
+ boolean[] array = new boolean[n];
+ for (int i = 0; i < n; ++i) {
+ array[i] = val.get(i);
+ }
+
+ writeBoolVector(array);
+ }
+
+ public final void writeInt8Vector(ArrayList<Byte> val) {
+ final int n = val.size();
+ byte[] array = new byte[n];
+ for (int i = 0; i < n; ++i) {
+ array[i] = val.get(i);
+ }
+
+ writeInt8Vector(array);
+ }
+
+ public final void writeInt16Vector(ArrayList<Short> val) {
+ final int n = val.size();
+ short[] array = new short[n];
+ for (int i = 0; i < n; ++i) {
+ array[i] = val.get(i);
+ }
+
+ writeInt16Vector(array);
+ }
+
+ public final void writeInt32Vector(ArrayList<Integer> val) {
+ final int n = val.size();
+ int[] array = new int[n];
+ for (int i = 0; i < n; ++i) {
+ array[i] = val.get(i);
+ }
+
+ writeInt32Vector(array);
+ }
+
+ public final void writeInt64Vector(ArrayList<Long> val) {
+ final int n = val.size();
+ long[] array = new long[n];
+ for (int i = 0; i < n; ++i) {
+ array[i] = val.get(i);
+ }
+
+ writeInt64Vector(array);
+ }
+
+ public final void writeFloatVector(ArrayList<Float> val) {
+ final int n = val.size();
+ float[] array = new float[n];
+ for (int i = 0; i < n; ++i) {
+ array[i] = val.get(i);
+ }
+
+ writeFloatVector(array);
+ }
+
+ public final void writeDoubleVector(ArrayList<Double> val) {
+ final int n = val.size();
+ double[] array = new double[n];
+ for (int i = 0; i < n; ++i) {
+ array[i] = val.get(i);
+ }
+
+ writeDoubleVector(array);
+ }
+
+ public final void writeStringVector(ArrayList<String> val) {
+ writeStringVector(val.toArray(new String[val.size()]));
+ }
public native final void writeStrongBinder(IHwBinder binder);
@@ -74,14 +151,60 @@
public native final double readDouble();
public native final String readString();
- public native final boolean[] readBoolVector();
- public native final byte[] readInt8Vector();
- public native final short[] readInt16Vector();
- public native final int[] readInt32Vector();
- public native final long[] readInt64Vector();
- public native final float[] readFloatVector();
- public native final double[] readDoubleVector();
- public native final String[] readStringVector();
+ private native final boolean[] readBoolVectorAsArray();
+ private native final byte[] readInt8VectorAsArray();
+ private native final short[] readInt16VectorAsArray();
+ private native final int[] readInt32VectorAsArray();
+ private native final long[] readInt64VectorAsArray();
+ private native final float[] readFloatVectorAsArray();
+ private native final double[] readDoubleVectorAsArray();
+ private native final String[] readStringVectorAsArray();
+
+ public final ArrayList<Boolean> readBoolVector() {
+ Boolean[] array = HwBlob.wrapArray(readBoolVectorAsArray());
+
+ return new ArrayList<Boolean>(Arrays.asList(array));
+ }
+
+ public final ArrayList<Byte> readInt8Vector() {
+ Byte[] array = HwBlob.wrapArray(readInt8VectorAsArray());
+
+ return new ArrayList<Byte>(Arrays.asList(array));
+ }
+
+ public final ArrayList<Short> readInt16Vector() {
+ Short[] array = HwBlob.wrapArray(readInt16VectorAsArray());
+
+ return new ArrayList<Short>(Arrays.asList(array));
+ }
+
+ public final ArrayList<Integer> readInt32Vector() {
+ Integer[] array = HwBlob.wrapArray(readInt32VectorAsArray());
+
+ return new ArrayList<Integer>(Arrays.asList(array));
+ }
+
+ public final ArrayList<Long> readInt64Vector() {
+ Long[] array = HwBlob.wrapArray(readInt64VectorAsArray());
+
+ return new ArrayList<Long>(Arrays.asList(array));
+ }
+
+ public final ArrayList<Float> readFloatVector() {
+ Float[] array = HwBlob.wrapArray(readFloatVectorAsArray());
+
+ return new ArrayList<Float>(Arrays.asList(array));
+ }
+
+ public final ArrayList<Double> readDoubleVector() {
+ Double[] array = HwBlob.wrapArray(readDoubleVectorAsArray());
+
+ return new ArrayList<Double>(Arrays.asList(array));
+ }
+
+ public final ArrayList<String> readStringVector() {
+ return new ArrayList<String>(Arrays.asList(readStringVectorAsArray()));
+ }
public native final IHwBinder readStrongBinder();
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index f9dee92..4eee854 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -20,6 +20,7 @@
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
+import android.webkit.WebViewZygote;
import dalvik.system.VMRuntime;
/**
@@ -133,6 +134,12 @@
public static final int CAMERASERVER_UID = 1047;
/**
+ * Defines the UID/GID for the WebView zygote process.
+ * @hide
+ */
+ public static final int WEBVIEW_ZYGOTE_UID = 1051;
+
+ /**
* Defines the start of a range of UIDs (and GIDs), going from this
* number to {@link #LAST_APPLICATION_UID} that are reserved for assigning
* to applications.
@@ -425,6 +432,22 @@
abi, instructionSet, appDataDir, zygoteArgs);
}
+ /** @hide */
+ public static final ProcessStartResult startWebView(final String processClass,
+ final String niceName,
+ int uid, int gid, int[] gids,
+ int debugFlags, int mountExternal,
+ int targetSdkVersion,
+ String seInfo,
+ String abi,
+ String instructionSet,
+ String appDataDir,
+ String[] zygoteArgs) {
+ return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
+ debugFlags, mountExternal, targetSdkVersion, seInfo,
+ abi, instructionSet, appDataDir, zygoteArgs);
+ }
+
/**
* Returns elapsed milliseconds of the time this process has run.
* @return Returns the number of milliseconds this process has return.
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index dbb9650..e4a12e8 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -302,7 +302,7 @@
/**
* Implement parsing and execution of a command. If it isn't a command you understand,
* call {@link #handleDefaultCommands(String)} and return its result as a last resort.
- * User {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
+ * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
* to process additional command line arguments. Command output can be written to
* {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}.
*
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index f050d76..c45fe5a 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -19,7 +19,9 @@
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.Zygote;
+import com.android.internal.util.Preconditions;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.IOException;
@@ -110,7 +112,8 @@
}
String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
- Log.i("Zygote", "Process: zygote socket opened, supported ABIS: " + abiListString);
+ Log.i("Zygote", "Process: zygote socket " + socketAddress + " opened, supported ABIS: "
+ + abiListString);
return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
Arrays.asList(abiListString.split(",")));
@@ -136,6 +139,13 @@
}
/**
+ * Lock object to protect access to the two ZygoteStates below. This lock must be
+ * acquired while communicating over the ZygoteState's socket, to prevent
+ * interleaved access.
+ */
+ private final Object mLock = new Object();
+
+ /**
* The state of the connection to the primary zygote.
*/
private ZygoteState primaryZygoteState;
@@ -207,6 +217,7 @@
*
* @throws ZygoteStartFailedEx if the query failed.
*/
+ @GuardedBy("mLock")
private static String getAbiList(BufferedWriter writer, DataInputStream inputStream)
throws IOException {
// Each query starts with the argument count (1 in this case)
@@ -233,6 +244,7 @@
*
* @throws ZygoteStartFailedEx if process start failed for any reason
*/
+ @GuardedBy("mLock")
private static Process.ProcessStartResult zygoteSendArgsAndGetResult(
ZygoteState zygoteState, ArrayList<String> args)
throws ZygoteStartFailedEx {
@@ -320,90 +332,90 @@
String appDataDir,
String[] extraArgs)
throws ZygoteStartFailedEx {
- synchronized(Process.class) {
- ArrayList<String> argsForZygote = new ArrayList<String>();
+ ArrayList<String> argsForZygote = new ArrayList<String>();
- // --runtime-args, --setuid=, --setgid=,
- // and --setgroups= must go first
- argsForZygote.add("--runtime-args");
- argsForZygote.add("--setuid=" + uid);
- argsForZygote.add("--setgid=" + gid);
- if ((debugFlags & Zygote.DEBUG_ENABLE_JNI_LOGGING) != 0) {
- argsForZygote.add("--enable-jni-logging");
- }
- if ((debugFlags & Zygote.DEBUG_ENABLE_SAFEMODE) != 0) {
- argsForZygote.add("--enable-safemode");
- }
- if ((debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) != 0) {
- argsForZygote.add("--enable-debugger");
- }
- if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) {
- argsForZygote.add("--enable-checkjni");
- }
- if ((debugFlags & Zygote.DEBUG_GENERATE_DEBUG_INFO) != 0) {
- argsForZygote.add("--generate-debug-info");
- }
- if ((debugFlags & Zygote.DEBUG_ALWAYS_JIT) != 0) {
- argsForZygote.add("--always-jit");
- }
- if ((debugFlags & Zygote.DEBUG_NATIVE_DEBUGGABLE) != 0) {
- argsForZygote.add("--native-debuggable");
- }
- if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
- argsForZygote.add("--enable-assert");
- }
- if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
- argsForZygote.add("--mount-external-default");
- } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
- argsForZygote.add("--mount-external-read");
- } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
- argsForZygote.add("--mount-external-write");
- }
- argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
+ // --runtime-args, --setuid=, --setgid=,
+ // and --setgroups= must go first
+ argsForZygote.add("--runtime-args");
+ argsForZygote.add("--setuid=" + uid);
+ argsForZygote.add("--setgid=" + gid);
+ if ((debugFlags & Zygote.DEBUG_ENABLE_JNI_LOGGING) != 0) {
+ argsForZygote.add("--enable-jni-logging");
+ }
+ if ((debugFlags & Zygote.DEBUG_ENABLE_SAFEMODE) != 0) {
+ argsForZygote.add("--enable-safemode");
+ }
+ if ((debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) != 0) {
+ argsForZygote.add("--enable-debugger");
+ }
+ if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) {
+ argsForZygote.add("--enable-checkjni");
+ }
+ if ((debugFlags & Zygote.DEBUG_GENERATE_DEBUG_INFO) != 0) {
+ argsForZygote.add("--generate-debug-info");
+ }
+ if ((debugFlags & Zygote.DEBUG_ALWAYS_JIT) != 0) {
+ argsForZygote.add("--always-jit");
+ }
+ if ((debugFlags & Zygote.DEBUG_NATIVE_DEBUGGABLE) != 0) {
+ argsForZygote.add("--native-debuggable");
+ }
+ if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
+ argsForZygote.add("--enable-assert");
+ }
+ if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
+ argsForZygote.add("--mount-external-default");
+ } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
+ argsForZygote.add("--mount-external-read");
+ } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
+ argsForZygote.add("--mount-external-write");
+ }
+ argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
- //TODO optionally enable debuger
- //argsForZygote.add("--enable-debugger");
+ //TODO optionally enable debuger
+ //argsForZygote.add("--enable-debugger");
- // --setgroups is a comma-separated list
- if (gids != null && gids.length > 0) {
- StringBuilder sb = new StringBuilder();
- sb.append("--setgroups=");
+ // --setgroups is a comma-separated list
+ if (gids != null && gids.length > 0) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("--setgroups=");
- int sz = gids.length;
- for (int i = 0; i < sz; i++) {
- if (i != 0) {
- sb.append(',');
- }
- sb.append(gids[i]);
+ int sz = gids.length;
+ for (int i = 0; i < sz; i++) {
+ if (i != 0) {
+ sb.append(',');
}
-
- argsForZygote.add(sb.toString());
+ sb.append(gids[i]);
}
- if (niceName != null) {
- argsForZygote.add("--nice-name=" + niceName);
+ argsForZygote.add(sb.toString());
+ }
+
+ if (niceName != null) {
+ argsForZygote.add("--nice-name=" + niceName);
+ }
+
+ if (seInfo != null) {
+ argsForZygote.add("--seinfo=" + seInfo);
+ }
+
+ if (instructionSet != null) {
+ argsForZygote.add("--instruction-set=" + instructionSet);
+ }
+
+ if (appDataDir != null) {
+ argsForZygote.add("--app-data-dir=" + appDataDir);
+ }
+
+ argsForZygote.add(processClass);
+
+ if (extraArgs != null) {
+ for (String arg : extraArgs) {
+ argsForZygote.add(arg);
}
+ }
- if (seInfo != null) {
- argsForZygote.add("--seinfo=" + seInfo);
- }
-
- if (instructionSet != null) {
- argsForZygote.add("--instruction-set=" + instructionSet);
- }
-
- if (appDataDir != null) {
- argsForZygote.add("--app-data-dir=" + appDataDir);
- }
-
- argsForZygote.add(processClass);
-
- if (extraArgs != null) {
- for (String arg : extraArgs) {
- argsForZygote.add(arg);
- }
- }
-
+ synchronized(mLock) {
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}
}
@@ -415,7 +427,9 @@
*/
public void establishZygoteConnectionForAbi(String abi) {
try {
- openZygoteSocketIfNeeded(abi);
+ synchronized(mLock) {
+ openZygoteSocketIfNeeded(abi);
+ }
} catch (ZygoteStartFailedEx ex) {
throw new RuntimeException("Unable to connect to zygote for abi: " + abi, ex);
}
@@ -423,9 +437,12 @@
/**
* Tries to open socket to Zygote process if not already open. If
- * already open, does nothing. May block and retry.
+ * already open, does nothing. May block and retry. Requires that mLock be held.
*/
+ @GuardedBy("mLock")
private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
+ Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held");
+
if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
try {
primaryZygoteState = ZygoteState.connect(mSocket);
@@ -453,4 +470,28 @@
throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);
}
+
+ /**
+ * Instructs the zygote to pre-load the classes and native libraries at the given paths
+ * for the specified abi. Not all zygotes support this function.
+ */
+ public void preloadPackageForAbi(String packagePath, String libsPath, String abi)
+ throws ZygoteStartFailedEx, IOException {
+ synchronized(mLock) {
+ ZygoteState state = openZygoteSocketIfNeeded(abi);
+ state.writer.write("3");
+ state.writer.newLine();
+
+ state.writer.write("--preload-package");
+ state.writer.newLine();
+
+ state.writer.write(packagePath);
+ state.writer.newLine();
+
+ state.writer.write(libsPath);
+ state.writer.newLine();
+
+ state.writer.flush();
+ }
+ }
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0f4afae..d3a978c 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6933,6 +6933,12 @@
public static final String DOCK_SOUNDS_ENABLED = "dock_sounds_enabled";
/**
+ * Whether to play a sound for dock events, only when an accessibility service is on.
+ * @hide
+ */
+ public static final String DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY = "dock_sounds_enabled_when_accessbility";
+
+ /**
* URI for the "device locked" (keyguard shown) sound.
* @hide
*/
@@ -7226,6 +7232,13 @@
*/
public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on";
+ /**
+ * Size of the event buffer for IP connectivity metrics.
+ * @hide
+ */
+ public static final String CONNECTIVITY_METRICS_BUFFER_SIZE =
+ "connectivity_metrics_buffer_size";
+
/** {@hide} */
public static final String NETSTATS_ENABLED = "netstats_enabled";
/** {@hide} */
@@ -8062,11 +8075,45 @@
public static final String PAC_CHANGE_DELAY = "pac_change_delay";
/**
- * Setting to turn off captive portal detection. Feature is enabled by
- * default and the setting needs to be set to 0 to disable it.
+ * Don't attempt to detect captive portals.
*
* @hide
*/
+ public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0;
+
+ /**
+ * When detecting a captive portal, display a notification that
+ * prompts the user to sign in.
+ *
+ * @hide
+ */
+ public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1;
+
+ /**
+ * When detecting a captive portal, immediately disconnect from the
+ * network and do not reconnect to that network in the future.
+ *
+ * @hide
+ */
+ public static final int CAPTIVE_PORTAL_MODE_AVOID = 2;
+
+ /**
+ * What to do when connecting a network that presents a captive portal.
+ * Must be one of the CAPTIVE_PORTAL_MODE_* constants above.
+ *
+ * The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT.
+ * @hide
+ */
+ public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
+
+ /**
+ * Setting to turn off captive portal detection. Feature is enabled by
+ * default and the setting needs to be set to 0 to disable it.
+ *
+ * @deprecated use CAPTIVE_PORTAL_MODE_IGNORE to disable captive portal detection
+ * @hide
+ */
+ @Deprecated
public static final String
CAPTIVE_PORTAL_DETECTION_ENABLED = "captive_portal_detection_enabled";
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 61d0a1e..4a956c6 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -36,9 +36,6 @@
private final Bundle mSignals;
private final int mUser;
- public static final String GROUP_KEY_OVERRIDE_KEY = "group_key_override";
- public static final String NEEDS_AUTOGROUPING_KEY = "autogroup_needed";
-
/**
* Create a notification adjustment.
*
diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java
index eb41f9e..106f172 100644
--- a/core/java/android/view/DisplayListCanvas.java
+++ b/core/java/android/view/DisplayListCanvas.java
@@ -51,9 +51,10 @@
if (node == null) throw new IllegalArgumentException("node cannot be null");
DisplayListCanvas canvas = sPool.acquire();
if (canvas == null) {
- canvas = new DisplayListCanvas(width, height);
+ canvas = new DisplayListCanvas(node, width, height);
} else {
- nResetDisplayListCanvas(canvas.mNativeCanvasWrapper, width, height);
+ nResetDisplayListCanvas(canvas.mNativeCanvasWrapper, node.mNativeRenderNode,
+ width, height);
}
canvas.mNode = node;
canvas.mWidth = width;
@@ -79,8 +80,8 @@
// Constructors
///////////////////////////////////////////////////////////////////////////
- private DisplayListCanvas(int width, int height) {
- super(nCreateDisplayListCanvas(width, height));
+ private DisplayListCanvas(@NonNull RenderNode node, int width, int height) {
+ super(nCreateDisplayListCanvas(node.mNativeRenderNode, width, height));
mDensity = 0; // disable bitmap density scaling
}
@@ -230,9 +231,10 @@
}
@FastNative
- private static native long nCreateDisplayListCanvas(int width, int height);
+ private static native long nCreateDisplayListCanvas(long node, int width, int height);
@FastNative
- private static native void nResetDisplayListCanvas(long canvas, int width, int height);
+ private static native void nResetDisplayListCanvas(long canvas, long node,
+ int width, int height);
@FastNative
private static native int nGetMaximumTextureWidth();
@FastNative
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 717b675..7917041 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -191,10 +191,10 @@
// If there is a change, the new Configuration is returned and the
// caller must call setNewConfiguration() sometime later.
Configuration updateOrientationFromAppTokens(in Configuration currentConfig,
- IBinder freezeThisOneIfNeeded);
- // Notify window manager of the new configuration. Returns an array of stack ids that's
- // affected by the update, ActivityManager should resize these stacks.
- int[] setNewConfiguration(in Configuration config);
+ IBinder freezeThisOneIfNeeded, int displayId);
+ // Notify window manager of the new display override configuration. Returns an array of stack
+ // ids that were affected by the update, ActivityManager should resize these stacks.
+ int[] setNewDisplayOverrideConfiguration(in Configuration overrideConfig, int displayId);
// Retrieves the new bounds after the configuration update evaluated by window manager.
Rect getBoundsForNewConfiguration(int stackId);
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 939f45f..b840f4a 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2566,6 +2566,13 @@
mProvider.getViewDelegate().onConfigurationChanged(newConfig);
}
+ /**
+ * Creates a new InputConnection for an InputMethod to interact with the WebView.
+ * This is similar to {@link View#onCreateInputConnection} but note that WebView
+ * calls InputConnection methods on a thread other than the UI thread.
+ * If these methods are overridden, then the overriding methods should respect
+ * thread restrictions when calling View methods or accessing data.
+ */
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return mProvider.getViewDelegate().onCreateInputConnection(outAttrs);
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 884a86c..f4ea90b 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -490,6 +490,9 @@
// Log and discard errors at this stage as we must not crash the system server.
Log.e(LOGTAG, "error preparing webview native library", t);
}
+
+ WebViewZygote.onWebViewProviderChanged(packageInfo);
+
return prepareWebViewInSystemServer(nativeLibs);
}
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
new file mode 100644
index 0000000..bc6e7b4
--- /dev/null
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2016 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 android.webkit;
+
+import android.content.pm.PackageInfo;
+import android.os.Build;
+import android.os.SystemService;
+import android.os.ZygoteProcess;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.TimeoutException;
+
+/** @hide */
+public class WebViewZygote {
+ private static final String LOGTAG = "WebViewZygote";
+
+ private static final String WEBVIEW_ZYGOTE_SERVICE_32 = "webview_zygote32";
+ private static final String WEBVIEW_ZYGOTE_SERVICE_64 = "webview_zygote64";
+
+ private static ZygoteProcess sZygote;
+
+ private static PackageInfo sPackage;
+
+ private static boolean sMultiprocessEnabled = false;
+
+ public static ZygoteProcess getProcess() {
+ connectToZygoteIfNeeded();
+ return sZygote;
+ }
+
+ public static String getPackageName() {
+ return sPackage.packageName;
+ }
+
+ public static void setMultiprocessEnabled(boolean enabled) {
+ sMultiprocessEnabled = enabled;
+
+ // When toggling between multi-process being on/off, start or stop the
+ // service. If it is enabled and the zygote is not yet started, bring up the service.
+ // Otherwise, bring down the service. The name may be null if the package
+ // information has not yet been resolved.
+ final String serviceName = getServiceName();
+ if (serviceName == null) return;
+
+ if (enabled && sZygote == null) {
+ SystemService.start(serviceName);
+ } else {
+ SystemService.stop(serviceName);
+ sZygote = null;
+ }
+ }
+
+ public static void onWebViewProviderChanged(PackageInfo packageInfo) {
+ sPackage = packageInfo;
+
+ // If multi-process is not enabled, then do not start the zygote service.
+ if (!sMultiprocessEnabled) {
+ return;
+ }
+
+ final String serviceName = getServiceName();
+
+ if (SystemService.isStopped(serviceName)) {
+ SystemService.start(serviceName);
+ } else if (sZygote != null) {
+ SystemService.restart(serviceName);
+ }
+
+ try {
+ SystemService.waitForState(serviceName, SystemService.State.RUNNING, 5000);
+ } catch (TimeoutException e) {
+ Log.e(LOGTAG, "Timed out waiting for " + serviceName);
+ return;
+ }
+
+ connectToZygoteIfNeeded();
+ }
+
+ private static String getServiceName() {
+ if (sPackage == null)
+ return null;
+
+ if (Arrays.asList(Build.SUPPORTED_64_BIT_ABIS).contains(
+ sPackage.applicationInfo.primaryCpuAbi)) {
+ return WEBVIEW_ZYGOTE_SERVICE_64;
+ }
+
+ return WEBVIEW_ZYGOTE_SERVICE_32;
+ }
+
+ private static void connectToZygoteIfNeeded() {
+ if (sZygote != null)
+ return;
+
+ if (sPackage == null) {
+ Log.e(LOGTAG, "Cannot connect to zygote, no package specified");
+ return;
+ }
+
+ final String serviceName = getServiceName();
+ if (!SystemService.isRunning(serviceName)) {
+ Log.e(LOGTAG, serviceName + " is not running");
+ return;
+ }
+
+ try {
+ sZygote = new ZygoteProcess("webview_zygote", null);
+
+ String packagePath = sPackage.applicationInfo.sourceDir;
+ String libsPath = sPackage.applicationInfo.nativeLibraryDir;
+
+ Log.d(LOGTAG, "Preloading package " + packagePath + " " + libsPath);
+ sZygote.preloadPackageForAbi(packagePath, libsPath, Build.SUPPORTED_ABIS[0]);
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Error connecting to " + serviceName, e);
+ sZygote = null;
+ }
+ }
+}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index bf49048..5eaabe7 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2345,7 +2345,7 @@
}
mCorrectionHighlighter.highlight(info);
- mUndoInputFilter.onCommitCorrection();
+ mUndoInputFilter.freezeLastEdit();
}
void onScrollChanged() {
@@ -2477,6 +2477,7 @@
}
mTextView.beginBatchEdit();
+ mUndoInputFilter.freezeLastEdit();
try {
final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY());
Object localState = event.getLocalState();
@@ -2526,6 +2527,7 @@
}
} finally {
mTextView.endBatchEdit();
+ mUndoInputFilter.freezeLastEdit();
}
}
@@ -5477,10 +5479,12 @@
// Expanding with start handle.
offset = getWordStart(offset);
startOffset = getWordEnd(mStartOffset);
+ if (startOffset == offset) {
+ offset = getNextCursorOffset(offset, false);
+ }
}
mLineSelectionIsOn = currLine;
- Selection.setSelection((Spannable) mTextView.getText(),
- startOffset, offset);
+ Selection.setSelection((Spannable) mTextView.getText(), startOffset, offset);
}
private void updateParagraphBasedSelection(MotionEvent event) {
@@ -5843,7 +5847,7 @@
return null;
}
- void onCommitCorrection() {
+ void freezeLastEdit() {
mEditor.mUndoManager.beginUpdate("Edit text");
EditOperation lastEdit = getLastEdit();
if (lastEdit != null) {
@@ -5904,7 +5908,6 @@
// Add this as the first edit.
if (DEBUG_UNDO) Log.d(TAG, "filter: adding first op " + edit);
um.addOperation(edit, UndoManager.MERGE_MODE_NONE);
- mPreviousOperationWasInSameBatchEdit = mIsUserEdit;
} else if (mergeMode == MERGE_EDIT_MODE_FORCE_MERGE) {
// Forced merges take priority because they could be the result of a non-user-edit
// change and this case should not create a new undo operation.
@@ -5916,7 +5919,6 @@
if (DEBUG_UNDO) Log.d(TAG, "non-user edit, new op " + edit);
um.commitState(mEditor.mUndoOwner);
um.addOperation(edit, UndoManager.MERGE_MODE_NONE);
- mPreviousOperationWasInSameBatchEdit = mIsUserEdit;
} else if (mergeMode == MERGE_EDIT_MODE_NORMAL && lastEdit.mergeWith(edit)) {
// Merge succeeded, nothing else to do.
if (DEBUG_UNDO) Log.d(TAG, "filter: merge succeeded, created " + lastEdit);
@@ -5925,8 +5927,8 @@
if (DEBUG_UNDO) Log.d(TAG, "filter: merge failed, adding " + edit);
um.commitState(mEditor.mUndoOwner);
um.addOperation(edit, UndoManager.MERGE_MODE_NONE);
- mPreviousOperationWasInSameBatchEdit = mIsUserEdit;
}
+ mPreviousOperationWasInSameBatchEdit = mIsUserEdit;
um.endUpdate();
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f1c3079..2f9c97b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3685,18 +3685,23 @@
}
/**
- * Makes the TextView at least this many lines tall.
+ * Sets the height of the TextView to be at least {@code minLines} tall.
+ * <p>
+ * This value is used for height calculation if LayoutParams does not force TextView to have an
+ * exact height. Setting this value overrides other previous minimum height configurations such
+ * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
+ * this value to 1.
*
- * Setting this value overrides any other (minimum) height setting. A single line TextView will
- * set this value to 1.
+ * @param minLines the minimum height of TextView in terms of number of lines
*
* @see #getMinLines()
+ * @see #setLines(int)
*
* @attr ref android.R.styleable#TextView_minLines
*/
@android.view.RemotableViewMethod
- public void setMinLines(int minlines) {
- mMinimum = minlines;
+ public void setMinLines(int minLines) {
+ mMinimum = minLines;
mMinMode = LINES;
requestLayout();
@@ -3704,10 +3709,14 @@
}
/**
- * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
- * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
+ * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
+ * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
+ *
+ * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
+ * height is not defined in lines
*
* @see #setMinLines(int)
+ * @see #setLines(int)
*
* @attr ref android.R.styleable#TextView_minLines
*/
@@ -3716,15 +3725,26 @@
}
/**
- * Makes the TextView at least this many pixels tall.
+ * Sets the height of the TextView to be at least {@code minPixels} tall.
+ * <p>
+ * This value is used for height calculation if LayoutParams does not force TextView to have an
+ * exact height. Setting this value overrides previous minimum height configurations such as
+ * {@link #setMinLines(int)} or {@link #setLines(int)}.
+ * <p>
+ * The value given here is different than {@link #setMinimumHeight(int)}. Between
+ * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
+ * used to decide the final height.
*
- * Setting this value overrides any other (minimum) number of lines setting.
+ * @param minPixels the minimum height of TextView in terms of pixels
+ *
+ * @see #getMinHeight()
+ * @see #setHeight(int)
*
* @attr ref android.R.styleable#TextView_minHeight
*/
@android.view.RemotableViewMethod
- public void setMinHeight(int minHeight) {
- mMinimum = minHeight;
+ public void setMinHeight(int minPixels) {
+ mMinimum = minPixels;
mMinMode = PIXELS;
requestLayout();
@@ -3732,10 +3752,14 @@
}
/**
- * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
- * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
+ * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
+ * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
+ *
+ * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
+ * defined in pixels
*
* @see #setMinHeight(int)
+ * @see #setHeight(int)
*
* @attr ref android.R.styleable#TextView_minHeight
*/
@@ -3744,15 +3768,22 @@
}
/**
- * Makes the TextView at most this many lines tall.
+ * Sets the height of the TextView to be at most {@code maxLines} tall.
+ * <p>
+ * This value is used for height calculation if LayoutParams does not force TextView to have an
+ * exact height. Setting this value overrides previous maximum height configurations such as
+ * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
*
- * Setting this value overrides any other (maximum) height setting.
+ * @param maxLines the maximum height of TextView in terms of number of lines
+ *
+ * @see #getMaxLines()
+ * @see #setLines(int)
*
* @attr ref android.R.styleable#TextView_maxLines
*/
@android.view.RemotableViewMethod
- public void setMaxLines(int maxlines) {
- mMaximum = maxlines;
+ public void setMaxLines(int maxLines) {
+ mMaximum = maxLines;
mMaxMode = LINES;
requestLayout();
@@ -3760,10 +3791,14 @@
}
/**
- * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
- * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
+ * Returns the maximum height of TextView in terms of number of lines or -1 if the
+ * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
+ *
+ * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
+ * is not defined in lines.
*
* @see #setMaxLines(int)
+ * @see #setLines(int)
*
* @attr ref android.R.styleable#TextView_maxLines
*/
@@ -3772,16 +3807,22 @@
}
/**
- * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
- * {@link #setMaxLines(int)} method.
+ * Sets the height of the TextView to be at most {@code maxPixels} tall.
+ * <p>
+ * This value is used for height calculation if LayoutParams does not force TextView to have an
+ * exact height. Setting this value overrides previous maximum height configurations such as
+ * {@link #setMaxLines(int)} or {@link #setLines(int)}.
*
- * Setting this value overrides any other (maximum) number of lines setting.
+ * @param maxPixels the maximum height of TextView in terms of pixels
+ *
+ * @see #getMaxHeight()
+ * @see #setHeight(int)
*
* @attr ref android.R.styleable#TextView_maxHeight
*/
@android.view.RemotableViewMethod
- public void setMaxHeight(int maxHeight) {
- mMaximum = maxHeight;
+ public void setMaxHeight(int maxPixels) {
+ mMaximum = maxPixels;
mMaxMode = PIXELS;
requestLayout();
@@ -3789,10 +3830,14 @@
}
/**
- * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
- * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
+ * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
+ * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
+ *
+ * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
+ * is not defined in pixels
*
* @see #setMaxHeight(int)
+ * @see #setHeight(int)
*
* @attr ref android.R.styleable#TextView_maxHeight
*/
@@ -3801,10 +3846,16 @@
}
/**
- * Makes the TextView exactly this many lines tall.
+ * Sets the height of the TextView to be exactly {@code lines} tall.
+ * <p>
+ * This value is used for height calculation if LayoutParams does not force TextView to have an
+ * exact height. Setting this value overrides previous minimum/maximum height configurations
+ * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
+ * set this value to 1.
*
- * Note that setting this value overrides any other (minimum / maximum) number of lines or
- * height setting. A single line TextView will set this value to 1.
+ * @param lines the exact height of the TextView in terms of lines
+ *
+ * @see #setHeight(int)
*
* @attr ref android.R.styleable#TextView_lines
*/
@@ -3818,12 +3869,15 @@
}
/**
- * Makes the TextView exactly this many pixels tall.
- * You could do the same thing by specifying this number in the
- * LayoutParams.
+ * Sets the height of the TextView to be exactly <code>pixels</code> tall.
+ * <p>
+ * This value is used for height calculation if LayoutParams does not force TextView to have an
+ * exact height. Setting this value overrides previous minimum/maximum height configurations
+ * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
*
- * Note that setting this value overrides any other (minimum / maximum) number of lines or
- * height setting.
+ * @param pixels the exact height of the TextView in terms of pixels
+ *
+ * @see #setLines(int)
*
* @attr ref android.R.styleable#TextView_height
*/
@@ -3837,13 +3891,22 @@
}
/**
- * Makes the TextView at least this many ems wide
+ * Sets the width of the TextView to be at least {@code minEms} wide.
+ * <p>
+ * This value is used for width calculation if LayoutParams does not force TextView to have an
+ * exact width. Setting this value overrides previous minimum width configurations such as
+ * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
+ *
+ * @param minEms the minimum width of TextView in terms of ems
+ *
+ * @see #getMinEms()
+ * @see #setEms(int)
*
* @attr ref android.R.styleable#TextView_minEms
*/
@android.view.RemotableViewMethod
- public void setMinEms(int minems) {
- mMinWidth = minems;
+ public void setMinEms(int minEms) {
+ mMinWidth = minEms;
mMinWidthMode = EMS;
requestLayout();
@@ -3851,8 +3914,11 @@
}
/**
- * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
- * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
+ * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
+ * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
+ *
+ * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
+ * defined in ems
*
* @see #setMinEms(int)
* @see #setEms(int)
@@ -3864,13 +3930,26 @@
}
/**
- * Makes the TextView at least this many pixels wide
+ * Sets the width of the TextView to be at least {@code minPixels} wide.
+ * <p>
+ * This value is used for width calculation if LayoutParams does not force TextView to have an
+ * exact width. Setting this value overrides previous minimum width configurations such as
+ * {@link #setMinEms(int)} or {@link #setEms(int)}.
+ * <p>
+ * The value given here is different than {@link #setMinimumWidth(int)}. Between
+ * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
+ * to decide the final width.
+ *
+ * @param minPixels the minimum width of TextView in terms of pixels
+ *
+ * @see #getMinWidth()
+ * @see #setWidth(int)
*
* @attr ref android.R.styleable#TextView_minWidth
*/
@android.view.RemotableViewMethod
- public void setMinWidth(int minpixels) {
- mMinWidth = minpixels;
+ public void setMinWidth(int minPixels) {
+ mMinWidth = minPixels;
mMinWidthMode = PIXELS;
requestLayout();
@@ -3878,8 +3957,11 @@
}
/**
- * @return the minimum width of the TextView, in pixels or -1 if the minimum width
- * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
+ * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
+ * using {@link #setMinEms(int)} or {@link #setEms(int)}.
+ *
+ * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
+ * defined in pixels
*
* @see #setMinWidth(int)
* @see #setWidth(int)
@@ -3891,13 +3973,22 @@
}
/**
- * Makes the TextView at most this many ems wide
+ * Sets the width of the TextView to be at most {@code maxEms} wide.
+ * <p>
+ * This value is used for width calculation if LayoutParams does not force TextView to have an
+ * exact width. Setting this value overrides previous maximum width configurations such as
+ * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
+ *
+ * @param maxEms the maximum width of TextView in terms of ems
+ *
+ * @see #getMaxEms()
+ * @see #setEms(int)
*
* @attr ref android.R.styleable#TextView_maxEms
*/
@android.view.RemotableViewMethod
- public void setMaxEms(int maxems) {
- mMaxWidth = maxems;
+ public void setMaxEms(int maxEms) {
+ mMaxWidth = maxEms;
mMaxWidthMode = EMS;
requestLayout();
@@ -3905,8 +3996,11 @@
}
/**
- * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
- * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
+ * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
+ * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
+ *
+ * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
+ * defined in ems
*
* @see #setMaxEms(int)
* @see #setEms(int)
@@ -3918,13 +4012,22 @@
}
/**
- * Makes the TextView at most this many pixels wide
+ * Sets the width of the TextView to be at most {@code maxPixels} wide.
+ * <p>
+ * This value is used for width calculation if LayoutParams does not force TextView to have an
+ * exact width. Setting this value overrides previous maximum width configurations such as
+ * {@link #setMaxEms(int)} or {@link #setEms(int)}.
+ *
+ * @param maxPixels the maximum width of TextView in terms of pixels
+ *
+ * @see #getMaxWidth()
+ * @see #setWidth(int)
*
* @attr ref android.R.styleable#TextView_maxWidth
*/
@android.view.RemotableViewMethod
- public void setMaxWidth(int maxpixels) {
- mMaxWidth = maxpixels;
+ public void setMaxWidth(int maxPixels) {
+ mMaxWidth = maxPixels;
mMaxWidthMode = PIXELS;
requestLayout();
@@ -3932,8 +4035,11 @@
}
/**
- * @return the maximum width of the TextView, in pixels or -1 if the maximum width
- * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
+ * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
+ * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
+ *
+ * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
+ * defined in pixels
*
* @see #setMaxWidth(int)
* @see #setWidth(int)
@@ -3945,12 +4051,15 @@
}
/**
- * Makes the TextView exactly this many ems wide
+ * Sets the width of the TextView to be exactly {@code ems} wide.
*
- * @see #setMaxEms(int)
- * @see #setMinEms(int)
- * @see #getMinEms()
- * @see #getMaxEms()
+ * This value is used for width calculation if LayoutParams does not force TextView to have an
+ * exact width. Setting this value overrides previous minimum/maximum configurations such as
+ * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
+ *
+ * @param ems the exact width of the TextView in terms of ems
+ *
+ * @see #setWidth(int)
*
* @attr ref android.R.styleable#TextView_ems
*/
@@ -3964,14 +4073,15 @@
}
/**
- * Makes the TextView exactly this many pixels wide.
- * You could do the same thing by specifying this number in the
- * LayoutParams.
+ * Sets the width of the TextView to be exactly {@code pixels} wide.
+ * <p>
+ * This value is used for width calculation if LayoutParams does not force TextView to have an
+ * exact width. Setting this value overrides previous minimum/maximum width configurations
+ * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
*
- * @see #setMaxWidth(int)
- * @see #setMinWidth(int)
- * @see #getMinWidth()
- * @see #getMaxWidth()
+ * @param pixels the exact width of the TextView in terms of pixels
+ *
+ * @see #setEms(int)
*
* @attr ref android.R.styleable#TextView_width
*/
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 4efcb09..789e60b 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -100,13 +100,13 @@
*/
public Toast(Context context) {
mContext = context;
- mTN = new TN();
+ mTN = new TN(context.getPackageName());
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
com.android.internal.R.integer.config_toastDefaultGravity);
}
-
+
/**
* Show the view for the specified duration.
*/
@@ -133,15 +133,9 @@
* after the appropriate duration.
*/
public void cancel() {
- mTN.hide();
-
- try {
- getService().cancelToast(mContext.getPackageName(), mTN);
- } catch (RemoteException e) {
- // Empty
- }
+ mTN.cancel();
}
-
+
/**
* Set the view to show.
* @see #getView
@@ -328,21 +322,37 @@
}
private static class TN extends ITransientNotification.Stub {
- final Runnable mHide = new Runnable() {
- @Override
- public void run() {
- handleHide();
- // Don't do this in handleHide() because it is also invoked by handleShow()
- mNextView = null;
- }
- };
-
private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+
+ private static final int SHOW = 0;
+ private static final int HIDE = 1;
+ private static final int CANCEL = 2;
final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
- IBinder token = (IBinder) msg.obj;
- handleShow(token);
+ switch (msg.what) {
+ case SHOW: {
+ IBinder token = (IBinder) msg.obj;
+ handleShow(token);
+ break;
+ }
+ case HIDE: {
+ handleHide();
+ // Don't do this in handleHide() because it is also invoked by handleShow()
+ mNextView = null;
+ break;
+ }
+ case CANCEL: {
+ handleHide();
+ // Don't do this in handleHide() because it is also invoked by handleShow()
+ mNextView = null;
+ try {
+ getService().cancelToast(mPackageName, TN.this);
+ } catch (RemoteException e) {
+ }
+ break;
+ }
+ }
}
};
@@ -358,10 +368,12 @@
WindowManager mWM;
+ String mPackageName;
+
static final long SHORT_DURATION_TIMEOUT = 4000;
static final long LONG_DURATION_TIMEOUT = 7000;
- TN() {
+ TN(String packageName) {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
final WindowManager.LayoutParams params = mParams;
@@ -374,6 +386,8 @@
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+
+ mPackageName = packageName;
}
/**
@@ -382,7 +396,7 @@
@Override
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
- mHandler.obtainMessage(0, windowToken).sendToTarget();
+ mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
/**
@@ -391,7 +405,12 @@
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
- mHandler.post(mHide);
+ mHandler.obtainMessage(HIDE).sendToTarget();
+ }
+
+ public void cancel() {
+ if (localLOGV) Log.v(TAG, "CANCEL: " + this);
+ mHandler.obtainMessage(CANCEL).sendToTarget();
}
public void handleShow(IBinder windowToken) {
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index 2ed7aa2..11dd0e8 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -16,6 +16,15 @@
package com.android.internal.os;
+import android.net.LocalSocket;
+import android.os.Build;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.IOException;
+
/**
* Startup class for the WebView zygote process.
*
@@ -26,7 +35,48 @@
class WebViewZygoteInit {
public static final String TAG = "WebViewZygoteInit";
+ private static ZygoteServer sServer;
+
+ private static class WebViewZygoteServer extends ZygoteServer {
+ @Override
+ protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
+ throws IOException {
+ return new WebViewZygoteConnection(socket, abiList);
+ }
+ }
+
+ private static class WebViewZygoteConnection extends ZygoteConnection {
+ WebViewZygoteConnection(LocalSocket socket, String abiList) throws IOException {
+ super(socket, abiList);
+ }
+
+ @Override
+ protected boolean handlePreloadPackage(String packagePath, String libsPath) {
+ // TODO: Use preload information to setup the ClassLoader.
+ return false;
+ }
+ }
+
public static void main(String argv[]) {
- throw new RuntimeException("Not implemented yet");
+ sServer = new WebViewZygoteServer();
+
+ // Zygote goes into its own process group.
+ try {
+ Os.setpgid(0, 0);
+ } catch (ErrnoException ex) {
+ throw new RuntimeException("Failed to setpgid(0,0)", ex);
+ }
+
+ try {
+ sServer.registerServerSocket("webview_zygote");
+ sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
+ sServer.closeServerSocket();
+ } catch (Zygote.MethodAndArgsCaller caller) {
+ caller.run();
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Fatal exception:", e);
+ }
+
+ System.exit(0);
}
}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index b8fa034..7edc938 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -44,6 +44,7 @@
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import libcore.io.IoUtils;
/**
@@ -170,6 +171,11 @@
return handleAbiListQuery();
}
+ if (parsedArgs.preloadPackage != null) {
+ return handlePreloadPackage(parsedArgs.preloadPackage,
+ parsedArgs.preloadPackageLibs);
+ }
+
if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
throw new ZygoteSecurityException("Client may not specify capabilities: " +
"permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
@@ -271,6 +277,10 @@
}
}
+ protected boolean handlePreloadPackage(String packagePath, String libsPath) {
+ throw new RuntimeException("Zyogte does not support package preloading");
+ }
+
/**
* Closes socket associated with this connection.
*/
@@ -376,6 +386,12 @@
String appDataDir;
/**
+ * Whether to preload a package, with the package path in the remainingArgs.
+ */
+ String preloadPackage;
+ String preloadPackageLibs;
+
+ /**
* Constructs instance and parses args
* @param args zygote command-line args
* @throws IllegalArgumentException
@@ -533,6 +549,9 @@
instructionSet = arg.substring(arg.indexOf('=') + 1);
} else if (arg.startsWith("--app-data-dir=")) {
appDataDir = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.equals("--preload-package")) {
+ preloadPackage = args[++curArg];
+ preloadPackageLibs = args[++curArg];
} else {
break;
}
@@ -542,6 +561,11 @@
if (args.length - curArg > 0) {
throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
}
+ } else if (preloadPackage != null) {
+ if (args.length - curArg > 0) {
+ throw new IllegalArgumentException(
+ "Unexpected arguments after --preload-package.");
+ }
} else {
if (!seenRuntimeArgs) {
throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index ab876410..126d9e7 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -19,6 +19,7 @@
import static android.system.OsConstants.POLLIN;
import android.net.LocalServerSocket;
+import android.net.LocalSocket;
import android.system.Os;
import android.system.ErrnoException;
import android.system.StructPollfd;
@@ -80,13 +81,18 @@
*/
private ZygoteConnection acceptCommandPeer(String abiList) {
try {
- return new ZygoteConnection(mServerSocket.accept(), abiList);
+ return createNewConnection(mServerSocket.accept(), abiList);
} catch (IOException ex) {
throw new RuntimeException(
"IOException during accept()", ex);
}
}
+ protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
+ throws IOException {
+ return new ZygoteConnection(socket, abiList);
+ }
+
/**
* Close and clean up zygote sockets. Called on shutdown and on the
* child's exit path.
diff --git a/core/java/com/android/internal/util/TokenBucket.java b/core/java/com/android/internal/util/TokenBucket.java
new file mode 100644
index 0000000..effb82b
--- /dev/null
+++ b/core/java/com/android/internal/util/TokenBucket.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 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.util;
+
+import android.os.SystemClock;
+
+import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
+import static com.android.internal.util.Preconditions.checkArgumentPositive;
+
+/**
+ * A class useful for rate-limiting or throttling that stores and distributes tokens.
+ *
+ * A TokenBucket starts with a fixed capacity of tokens, an initial amount of tokens, and
+ * a fixed filling period (in milliseconds).
+ *
+ * For every filling period, the bucket gains one token, up to its maximum capacity from
+ * which point tokens simply overflow and are lost. Tokens can be obtained one by one or n by n.
+ *
+ * The available amount of tokens is computed lazily when the bucket state is inspected.
+ * Therefore it is purely synchronous and does not involve any asynchronous activity.
+ * It is not synchronized in any way and not a thread-safe object.
+ */
+public class TokenBucket {
+
+ private final int mFillDelta; // Time in ms it takes to generate one token.
+ private final int mCapacity; // Maximum number of tokens that can be stored.
+ private long mLastFill; // Last time in ms the bucket generated tokens.
+ private int mAvailable; // Current number of available tokens.
+
+ /**
+ * Create a new TokenBucket.
+ * @param deltaMs the time in milliseconds it takes to generate a new token.
+ * Must be strictly positive.
+ * @param capacity the maximum token capacity. Must be strictly positive.
+ * @param tokens the starting amount of token. Must be positive or zero.
+ */
+ public TokenBucket(int deltaMs, int capacity, int tokens) {
+ mFillDelta = checkArgumentPositive(deltaMs, "deltaMs must be strictly positive");
+ mCapacity = checkArgumentPositive(capacity, "capacity must be strictly positive");
+ mAvailable = Math.min(checkArgumentNonnegative(tokens), mCapacity);
+ mLastFill = scaledTime();
+ }
+
+ /**
+ * Create a new TokenBucket that starts completely filled.
+ * @param deltaMs the time in milliseconds it takes to generate a new token.
+ * Must be strictly positive.
+ * @param capacity the maximum token capacity. Must be strictly positive.
+ */
+ public TokenBucket(int deltaMs, int capacity) {
+ this(deltaMs, capacity, capacity);
+ }
+
+ /** Reset this TokenBucket and set its number of available tokens. */
+ public void reset(int tokens) {
+ checkArgumentNonnegative(tokens);
+ mAvailable = Math.min(tokens, mCapacity);
+ mLastFill = scaledTime();
+ }
+
+ /** Returns this TokenBucket maximum token capacity. */
+ public int capacity() {
+ return mCapacity;
+ }
+
+ /** Returns this TokenBucket currently number of available tokens. */
+ public int available() {
+ fill();
+ return mAvailable;
+ }
+
+ /** Returns true if this TokenBucket as one or more tokens available. */
+ public boolean has() {
+ fill();
+ return mAvailable > 0;
+ }
+
+ /** Consumes a token from this TokenBucket and returns true if a token is available. */
+ public boolean get() {
+ return (get(1) == 1);
+ }
+
+ /**
+ * Try to consume many tokens from this TokenBucket.
+ * @param n the number of tokens to consume.
+ * @return the number of tokens that were actually consumed.
+ */
+ public int get(int n) {
+ fill();
+ if (n <= 0) {
+ return 0;
+ }
+ if (n > mAvailable) {
+ int got = mAvailable;
+ mAvailable = 0;
+ return got;
+ }
+ mAvailable -= n;
+ return n;
+ }
+
+ private void fill() {
+ final long now = scaledTime();
+ final int diff = (int) (now - mLastFill);
+ mAvailable = Math.min(mCapacity, mAvailable + diff);
+ mLastFill = now;
+ }
+
+ private long scaledTime() {
+ return SystemClock.elapsedRealtime() / mFillDelta;
+ }
+}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 0e07bf8..71252fb 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.PasswordMetrics;
import android.app.trust.IStrongAuthTracker;
import android.app.trust.TrustManager;
import android.content.ComponentName;
@@ -137,10 +138,6 @@
private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents";
private static final String IS_TRUST_USUALLY_MANAGED = "lockscreen.istrustusuallymanaged";
- // Maximum allowed number of repeated or ordered characters in a sequence before we'll
- // consider it a complex PIN/password.
- public static final int MAX_ALLOWED_SEQUENCE = 3;
-
public static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_";
public static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";
@@ -593,8 +590,7 @@
setCredentialRequiredToDecrypt(false);
}
- getDevicePolicyManager().setActivePasswordState(
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0, userHandle);
+ getDevicePolicyManager().setActivePasswordState(new PasswordMetrics(), userHandle);
onAfterChangingPassword(userHandle);
}
@@ -665,8 +661,8 @@
setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId);
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
- dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING,
- pattern.size(), 0, 0, 0, 0, 0, 0, userId);
+ dpm.setActivePasswordState(new PasswordMetrics(
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern.size()), userId);
onAfterChangingPassword(userId);
} catch (RemoteException re) {
Log.e(TAG, "Couldn't save lock pattern " + re);
@@ -736,96 +732,6 @@
return getDeviceOwnerInfo() != null;
}
- /**
- * Compute the password quality from the given password string.
- */
- static public int computePasswordQuality(String password) {
- boolean hasDigit = false;
- boolean hasNonDigit = false;
- final int len = password.length();
- for (int i = 0; i < len; i++) {
- if (Character.isDigit(password.charAt(i))) {
- hasDigit = true;
- } else {
- hasNonDigit = true;
- }
- }
-
- if (hasNonDigit && hasDigit) {
- return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
- }
- if (hasNonDigit) {
- return DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
- }
- if (hasDigit) {
- return maxLengthSequence(password) > MAX_ALLOWED_SEQUENCE
- ? DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
- : DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
- }
- return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
- }
-
- private static int categoryChar(char c) {
- if ('a' <= c && c <= 'z') return 0;
- if ('A' <= c && c <= 'Z') return 1;
- if ('0' <= c && c <= '9') return 2;
- return 3;
- }
-
- private static int maxDiffCategory(int category) {
- if (category == 0 || category == 1) return 1;
- else if (category == 2) return 10;
- return 0;
- }
-
- /*
- * Returns the maximum length of a sequential characters. A sequence is defined as
- * monotonically increasing characters with a constant interval or the same character repeated.
- *
- * For example:
- * maxLengthSequence("1234") == 4
- * maxLengthSequence("1234abc") == 4
- * maxLengthSequence("aabc") == 3
- * maxLengthSequence("qwertyuio") == 1
- * maxLengthSequence("@ABC") == 3
- * maxLengthSequence(";;;;") == 4 (anything that repeats)
- * maxLengthSequence(":;<=>") == 1 (ordered, but not composed of alphas or digits)
- *
- * @param string the pass
- * @return the number of sequential letters or digits
- */
- public static int maxLengthSequence(String string) {
- if (string.length() == 0) return 0;
- char previousChar = string.charAt(0);
- int category = categoryChar(previousChar); //current category of the sequence
- int diff = 0; //difference between two consecutive characters
- boolean hasDiff = false; //if we are currently targeting a sequence
- int maxLength = 0; //maximum length of a sequence already found
- int startSequence = 0; //where the current sequence started
- for (int current = 1; current < string.length(); current++) {
- char currentChar = string.charAt(current);
- int categoryCurrent = categoryChar(currentChar);
- int currentDiff = (int) currentChar - (int) previousChar;
- if (categoryCurrent != category || Math.abs(currentDiff) > maxDiffCategory(category)) {
- maxLength = Math.max(maxLength, current - startSequence);
- startSequence = current;
- hasDiff = false;
- category = categoryCurrent;
- }
- else {
- if(hasDiff && currentDiff != diff) {
- maxLength = Math.max(maxLength, current - startSequence);
- startSequence = current - 1;
- }
- diff = currentDiff;
- hasDiff = true;
- }
- previousChar = currentChar;
- }
- maxLength = Math.max(maxLength, string.length() - startSequence);
- return maxLength;
- }
-
/** Update the encryption password if it is enabled **/
private void updateEncryptionPassword(final int type, final String password) {
if (!isDeviceEncryptionEnabled()) {
@@ -871,7 +777,8 @@
getLockSettings().setLockPassword(password, savedPassword, userHandle);
getLockSettings().setSeparateProfileChallengeEnabled(userHandle, true, null);
- int computedQuality = computePasswordQuality(password);
+ final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password);
+ final int computedQuality = metrics.quality;
// Update the device encryption password.
if (userHandle == UserHandle.USER_SYSTEM
@@ -891,36 +798,11 @@
setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
- int letters = 0;
- int uppercase = 0;
- int lowercase = 0;
- int numbers = 0;
- int symbols = 0;
- int nonletter = 0;
- for (int i = 0; i < password.length(); i++) {
- char c = password.charAt(i);
- if (c >= 'A' && c <= 'Z') {
- letters++;
- uppercase++;
- } else if (c >= 'a' && c <= 'z') {
- letters++;
- lowercase++;
- } else if (c >= '0' && c <= '9') {
- numbers++;
- nonletter++;
- } else {
- symbols++;
- nonletter++;
- }
- }
- dpm.setActivePasswordState(Math.max(quality, computedQuality),
- password.length(), letters, uppercase, lowercase,
- numbers, symbols, nonletter, userHandle);
+ metrics.quality = Math.max(quality, metrics.quality);
+ dpm.setActivePasswordState(metrics, userHandle);
} else {
// The password is not anything.
- dpm.setActivePasswordState(
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- 0, 0, 0, 0, 0, 0, 0, userHandle);
+ dpm.setActivePasswordState(new PasswordMetrics(), userHandle);
}
// Add the password to the password history. We assume all
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 9ed1588..467ec37 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -17,7 +17,7 @@
#include "android_nio_utils.h"
#include "CreateJavaOutputStreamAdaptor.h"
#include <hwui/Paint.h>
-#include <hwui/PixelRef.h>
+#include <hwui/Bitmap.h>
#include <renderthread/RenderProxy.h>
#include "core_jni_helpers.h"
@@ -37,25 +37,28 @@
namespace android {
-class Bitmap {
+class BitmapWrapper {
public:
- Bitmap(PixelRef* pixelRef)
- : mPixelRef(pixelRef) { }
+ BitmapWrapper(Bitmap* bitmap)
+ : mBitmap(bitmap) { }
void freePixels() {
- mInfo = mPixelRef->info();
- mHasHardwareMipMap = mPixelRef->hasHardwareMipMap();
- mAllocationSize = mPixelRef->getAllocationByteCount();
- mRowBytes = mPixelRef->rowBytes();
- mGenerationId = mPixelRef->getGenerationID();
- mPixelRef.reset();
+ mInfo = mBitmap->info();
+ mHasHardwareMipMap = mBitmap->hasHardwareMipMap();
+ mAllocationSize = mBitmap->getAllocationByteCount();
+ mRowBytes = mBitmap->rowBytes();
+ mGenerationId = mBitmap->getGenerationID();
+ mBitmap.reset();
}
bool valid() {
- return !!mPixelRef;
+ return mBitmap;
}
- PixelRef* pixelRef() { return mPixelRef.get(); }
+ Bitmap& bitmap() {
+ assertValid();
+ return *mBitmap;
+ }
void assertValid() {
LOG_ALWAYS_FATAL_IF(!valid(), "Error, cannot access an invalid/free'd bitmap here!");
@@ -63,58 +66,58 @@
void getSkBitmap(SkBitmap* outBitmap) {
assertValid();
- mPixelRef->getSkBitmap(outBitmap);
+ mBitmap->getSkBitmap(outBitmap);
}
bool hasHardwareMipMap() {
- if (mPixelRef) {
- return mPixelRef->hasHardwareMipMap();
+ if (mBitmap) {
+ return mBitmap->hasHardwareMipMap();
}
return mHasHardwareMipMap;
}
void setHasHardwareMipMap(bool hasMipMap) {
assertValid();
- mPixelRef->setHasHardwareMipMap(hasMipMap);
+ mBitmap->setHasHardwareMipMap(hasMipMap);
}
void setAlphaType(SkAlphaType alphaType) {
assertValid();
- mPixelRef->setAlphaType(alphaType);
+ mBitmap->setAlphaType(alphaType);
}
const SkImageInfo& info() {
- if (mPixelRef) {
- return mPixelRef->info();
+ if (mBitmap) {
+ return mBitmap->info();
}
return mInfo;
}
size_t getAllocationByteCount() const {
- if (mPixelRef) {
- return mPixelRef->getAllocationByteCount();
+ if (mBitmap) {
+ return mBitmap->getAllocationByteCount();
}
return mAllocationSize;
}
size_t rowBytes() const {
- if (mPixelRef) {
- return mPixelRef->rowBytes();
+ if (mBitmap) {
+ return mBitmap->rowBytes();
}
return mRowBytes;
}
uint32_t getGenerationID() const {
- if (mPixelRef) {
- return mPixelRef->getGenerationID();
+ if (mBitmap) {
+ return mBitmap->getGenerationID();
}
return mGenerationId;
}
- ~Bitmap() { }
+ ~BitmapWrapper() { }
private:
- sk_sp<PixelRef> mPixelRef;
+ sk_sp<Bitmap> mBitmap;
SkImageInfo mInfo;
bool mHasHardwareMipMap;
size_t mAllocationSize;
@@ -127,22 +130,22 @@
class LocalScopedBitmap {
public:
explicit LocalScopedBitmap(jlong bitmapHandle)
- : mBitmap(reinterpret_cast<Bitmap*>(bitmapHandle)) {}
+ : mBitmapWrapper(reinterpret_cast<BitmapWrapper*>(bitmapHandle)) {}
- Bitmap* operator->() {
- return mBitmap;
+ BitmapWrapper* operator->() {
+ return mBitmapWrapper;
}
void* pixels() {
- return mBitmap->pixelRef()->pixels();
+ return mBitmapWrapper->bitmap().pixels();
}
bool valid() {
- return mBitmap && mBitmap->valid();
+ return mBitmapWrapper && mBitmapWrapper->valid();
}
private:
- Bitmap* mBitmap;
+ BitmapWrapper* mBitmapWrapper;
};
namespace bitmap {
@@ -175,18 +178,18 @@
return env->CallIntMethod(javaBitmap, gBitmap_getAllocationByteCountMethodID);
}
-jobject createBitmap(JNIEnv* env, PixelRef* pixelRef,
+jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
int density) {
bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
// The caller needs to have already set the alpha type properly, so the
// native SkBitmap stays in sync with the Java Bitmap.
- assert_premultiplied(pixelRef->info(), isPremultiplied);
- Bitmap* bitmap = new Bitmap(pixelRef);
+ assert_premultiplied(bitmap->info(), isPremultiplied);
+ BitmapWrapper* bitmapWrapper = new BitmapWrapper(bitmap);
jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
- reinterpret_cast<jlong>(bitmap), pixelRef->width(), pixelRef->height(), density, isMutable,
- isPremultiplied, ninePatchChunk, ninePatchInsets);
+ reinterpret_cast<jlong>(bitmapWrapper), bitmap->width(), bitmap->height(), density,
+ isMutable, isPremultiplied, ninePatchChunk, ninePatchInsets);
if (env->ExceptionCheck() != 0) {
ALOGE("*** Uncaught exception returned from Java call!\n");
@@ -200,14 +203,19 @@
bitmap->getSkBitmap(outBitmap);
}
-PixelRef* toPixelRef(JNIEnv* env, jobject bitmap) {
+Bitmap& toBitmap(JNIEnv* env, jobject bitmap) {
SkASSERT(env);
SkASSERT(bitmap);
SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr);
LocalScopedBitmap localBitmap(bitmapHandle);
- localBitmap->assertValid();
- return localBitmap->pixelRef();
+ return localBitmap->bitmap();
+}
+
+Bitmap& toBitmap(JNIEnv* env, jlong bitmapHandle) {
+ SkASSERT(env);
+ LocalScopedBitmap localBitmap(bitmapHandle);
+ return localBitmap->bitmap();
}
} // namespace bitmap
@@ -548,7 +556,7 @@
bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType,
GraphicsJNI::defaultColorSpace()));
- PixelRef* nativeBitmap = GraphicsJNI::allocateHeapPixelRef(&bitmap, NULL);
+ sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap, NULL);
if (!nativeBitmap) {
return NULL;
}
@@ -558,14 +566,13 @@
0, 0, width, height, bitmap);
}
- return createBitmap(env, nativeBitmap,
- getPremulBitmapCreateFlags(isMutable));
+ return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable));
}
static jobject Bitmap_copy(JNIEnv* env, jobject, jlong srcHandle,
jint dstConfigHandle, jboolean isMutable) {
SkBitmap src;
- reinterpret_cast<Bitmap*>(srcHandle)->getSkBitmap(&src);
+ reinterpret_cast<BitmapWrapper*>(srcHandle)->getSkBitmap(&src);
SkColorType dstCT = GraphicsJNI::legacyBitmapConfigToColorType(dstConfigHandle);
SkBitmap result;
HeapAllocator allocator;
@@ -573,41 +580,41 @@
if (!src.copyTo(&result, dstCT, &allocator)) {
return NULL;
}
- auto pixelRef = allocator.getStorageObjAndReset();
- return createBitmap(env, pixelRef, getPremulBitmapCreateFlags(isMutable));
+ auto bitmap = allocator.getStorageObjAndReset();
+ return createBitmap(env, bitmap, getPremulBitmapCreateFlags(isMutable));
}
-static PixelRef* Bitmap_copyAshmemImpl(JNIEnv* env, SkBitmap& src, SkColorType& dstCT) {
+static Bitmap* Bitmap_copyAshmemImpl(JNIEnv* env, SkBitmap& src, SkColorType& dstCT) {
SkBitmap result;
AshmemPixelAllocator allocator(env);
if (!src.copyTo(&result, dstCT, &allocator)) {
return NULL;
}
- auto pixelRef = allocator.getStorageObjAndReset();
- pixelRef->setImmutable();
- return pixelRef;
+ auto bitmap = allocator.getStorageObjAndReset();
+ bitmap->setImmutable();
+ return bitmap;
}
static jobject Bitmap_copyAshmem(JNIEnv* env, jobject, jlong srcHandle) {
SkBitmap src;
- reinterpret_cast<Bitmap*>(srcHandle)->getSkBitmap(&src);
+ reinterpret_cast<BitmapWrapper*>(srcHandle)->getSkBitmap(&src);
SkColorType dstCT = src.colorType();
- auto pixelRef = Bitmap_copyAshmemImpl(env, src, dstCT);
- jobject ret = createBitmap(env, pixelRef, getPremulBitmapCreateFlags(false));
+ auto bitmap = Bitmap_copyAshmemImpl(env, src, dstCT);
+ jobject ret = createBitmap(env, bitmap, getPremulBitmapCreateFlags(false));
return ret;
}
static jobject Bitmap_copyAshmemConfig(JNIEnv* env, jobject, jlong srcHandle, jint dstConfigHandle) {
SkBitmap src;
- reinterpret_cast<Bitmap*>(srcHandle)->getSkBitmap(&src);
+ reinterpret_cast<BitmapWrapper*>(srcHandle)->getSkBitmap(&src);
SkColorType dstCT = GraphicsJNI::legacyBitmapConfigToColorType(dstConfigHandle);
- auto pixelRef = Bitmap_copyAshmemImpl(env, src, dstCT);
- jobject ret = createBitmap(env, pixelRef, getPremulBitmapCreateFlags(false));
+ auto bitmap = Bitmap_copyAshmemImpl(env, src, dstCT);
+ jobject ret = createBitmap(env, bitmap, getPremulBitmapCreateFlags(false));
return ret;
}
-static void Bitmap_destruct(Bitmap* bitmap) {
+static void Bitmap_destruct(BitmapWrapper* bitmap) {
delete bitmap;
}
@@ -647,7 +654,7 @@
// Otherwise respect the premultiplied request.
alphaType = requestPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
}
- bitmap->pixelRef()->reconfigure(SkImageInfo::Make(width, height, colorType, alphaType,
+ bitmap->bitmap().reconfigure(SkImageInfo::Make(width, height, colorType, alphaType,
sk_sp<SkColorSpace>(bitmap->info().colorSpace())));
}
@@ -832,7 +839,7 @@
}
// Map the bitmap in place from the ashmem region if possible otherwise copy.
- PixelRef* nativeBitmap;
+ sk_sp<Bitmap> nativeBitmap;
if (blob.fd() >= 0 && (blob.isMutable() || !isMutable) && (size >= ASHMEM_BITMAP_MIN_SIZE)) {
#if DEBUG_PARCEL
ALOGD("Bitmap.createFromParcel: mapped contents of %s bitmap from %s blob "
@@ -853,8 +860,8 @@
}
// Map the pixels in place and take ownership of the ashmem region.
- nativeBitmap = GraphicsJNI::mapAshmemPixelRef(env, bitmap.get(),
- ctable, dupFd, const_cast<void*>(blob.data()), size, !isMutable);
+ nativeBitmap = sk_sp<Bitmap>(GraphicsJNI::mapAshmemBitmap(env, bitmap.get(),
+ ctable, dupFd, const_cast<void*>(blob.data()), size, !isMutable));
SkSafeUnref(ctable);
if (!nativeBitmap) {
close(dupFd);
@@ -880,7 +887,7 @@
#endif
// Copy the pixels into a new buffer.
- nativeBitmap = GraphicsJNI::allocateHeapPixelRef(bitmap.get(), ctable);
+ nativeBitmap = Bitmap::allocateHeapBitmap(bitmap.get(), ctable);
SkSafeUnref(ctable);
if (!nativeBitmap) {
blob.release();
@@ -895,7 +902,7 @@
blob.release();
}
- return createBitmap(env, nativeBitmap,
+ return createBitmap(env, nativeBitmap.release(),
getPremulBitmapCreateFlags(isMutable), NULL, NULL, density);
}
@@ -911,8 +918,8 @@
android::Parcel* p = android::parcelForJavaObject(env, parcel);
SkBitmap bitmap;
- auto androidBitmap = reinterpret_cast<Bitmap*>(bitmapHandle);
- androidBitmap->getSkBitmap(&bitmap);
+ auto bitmapWrapper = reinterpret_cast<BitmapWrapper*>(bitmapHandle);
+ bitmapWrapper->getSkBitmap(&bitmap);
sk_sp<SkColorSpace> sRGB = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
bool isSRGB = bitmap.colorSpace() == sRGB.get();
@@ -942,7 +949,7 @@
// Transfer the underlying ashmem region if we have one and it's immutable.
android::status_t status;
- int fd = androidBitmap->pixelRef()->getAshmemFd();
+ int fd = bitmapWrapper->bitmap().getAshmemFd();
if (fd >= 0 && !isMutable && p->allowFds()) {
#if DEBUG_PARCEL
ALOGD("Bitmap.writeToParcel: transferring immutable bitmap's ashmem fd as "
@@ -992,7 +999,7 @@
jlong srcHandle, jlong paintHandle,
jintArray offsetXY) {
SkBitmap src;
- reinterpret_cast<Bitmap*>(srcHandle)->getSkBitmap(&src);
+ reinterpret_cast<BitmapWrapper*>(srcHandle)->getSkBitmap(&src);
const android::Paint* paint = reinterpret_cast<android::Paint*>(paintHandle);
SkIPoint offset;
SkBitmap dst;
@@ -1021,7 +1028,7 @@
static jint Bitmap_getPixel(JNIEnv* env, jobject, jlong bitmapHandle,
jint x, jint y) {
SkBitmap bitmap;
- reinterpret_cast<Bitmap*>(bitmapHandle)->getSkBitmap(&bitmap);
+ reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap);
SkAutoLockPixels alp(bitmap);
ToColorProc proc = ChooseToColorProc(bitmap);
@@ -1042,7 +1049,7 @@
jintArray pixelArray, jint offset, jint stride,
jint x, jint y, jint width, jint height) {
SkBitmap bitmap;
- reinterpret_cast<Bitmap*>(bitmapHandle)->getSkBitmap(&bitmap);
+ reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap);
SkAutoLockPixels alp(bitmap);
ToColorProc proc = ChooseToColorProc(bitmap);
@@ -1070,7 +1077,7 @@
static void Bitmap_setPixel(JNIEnv* env, jobject, jlong bitmapHandle,
jint x, jint y, jint colorHandle) {
SkBitmap bitmap;
- reinterpret_cast<Bitmap*>(bitmapHandle)->getSkBitmap(&bitmap);
+ reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap);
SkColor color = static_cast<SkColor>(colorHandle);
SkAutoLockPixels alp(bitmap);
if (NULL == bitmap.getPixels()) {
@@ -1090,7 +1097,7 @@
jintArray pixelArray, jint offset, jint stride,
jint x, jint y, jint width, jint height) {
SkBitmap bitmap;
- reinterpret_cast<Bitmap*>(bitmapHandle)->getSkBitmap(&bitmap);
+ reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap);
GraphicsJNI::SetPixels(env, pixelArray, offset, stride,
x, y, width, height, bitmap);
}
@@ -1098,7 +1105,7 @@
static void Bitmap_copyPixelsToBuffer(JNIEnv* env, jobject,
jlong bitmapHandle, jobject jbuffer) {
SkBitmap bitmap;
- reinterpret_cast<Bitmap*>(bitmapHandle)->getSkBitmap(&bitmap);
+ reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap);
SkAutoLockPixels alp(bitmap);
const void* src = bitmap.getPixels();
@@ -1113,7 +1120,7 @@
static void Bitmap_copyPixelsFromBuffer(JNIEnv* env, jobject,
jlong bitmapHandle, jobject jbuffer) {
SkBitmap bitmap;
- reinterpret_cast<Bitmap*>(bitmapHandle)->getSkBitmap(&bitmap);
+ reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap);
SkAutoLockPixels alp(bitmap);
void* dst = bitmap.getPixels();
@@ -1129,8 +1136,8 @@
jlong bm1Handle) {
SkBitmap bm0;
SkBitmap bm1;
- reinterpret_cast<Bitmap*>(bm0Handle)->getSkBitmap(&bm0);
- reinterpret_cast<Bitmap*>(bm1Handle)->getSkBitmap(&bm1);
+ reinterpret_cast<BitmapWrapper*>(bm0Handle)->getSkBitmap(&bm0);
+ reinterpret_cast<BitmapWrapper*>(bm1Handle)->getSkBitmap(&bm1);
if (bm0.width() != bm1.width() ||
bm0.height() != bm1.height() ||
bm0.colorType() != bm1.colorType() ||
@@ -1190,17 +1197,15 @@
static jlong Bitmap_refPixelRef(JNIEnv* env, jobject, jlong bitmapHandle) {
LocalScopedBitmap bitmap(bitmapHandle);
- SkPixelRef* pixelRef = bitmap->pixelRef();
- SkSafeRef(pixelRef);
- return reinterpret_cast<jlong>(pixelRef);
+ SkPixelRef& pixelRef = bitmap->bitmap();
+ pixelRef.ref();
+ return reinterpret_cast<jlong>(&pixelRef);
}
static void Bitmap_prepareToDraw(JNIEnv* env, jobject, jlong bitmapPtr) {
LocalScopedBitmap bitmapHandle(bitmapPtr);
if (!bitmapHandle.valid()) return;
- SkBitmap bitmap;
- bitmapHandle->getSkBitmap(&bitmap);
- android::uirenderer::renderthread::RenderProxy::prepareToDraw(bitmap);
+ android::uirenderer::renderthread::RenderProxy::prepareToDraw(bitmapHandle->bitmap());
}
static jint Bitmap_getAllocationByteCount(JNIEnv* env, jobject, jlong bitmapPtr) {
diff --git a/core/jni/android/graphics/Bitmap.h b/core/jni/android/graphics/Bitmap.h
index 588a99c..387a128 100644
--- a/core/jni/android/graphics/Bitmap.h
+++ b/core/jni/android/graphics/Bitmap.h
@@ -24,7 +24,7 @@
namespace android {
-class PixelRef;
+class Bitmap;
namespace bitmap {
@@ -34,14 +34,15 @@
kBitmapCreateFlag_Premultiplied = 0x2,
};
-jobject createBitmap(JNIEnv* env, PixelRef* bitmap,
+jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
int bitmapCreateFlags, jbyteArray ninePatchChunk = NULL,
jobject ninePatchInsets = NULL, int density = -1);
void toSkBitmap(jlong bitmapHandle, SkBitmap* outBitmap);
-PixelRef* toPixelRef(JNIEnv* env, jobject bitmap);
+Bitmap& toBitmap(JNIEnv* env, jobject bitmap);
+Bitmap& toBitmap(JNIEnv* env, jlong bitmapHandle);
/** Reinitialize a bitmap. bitmap must already have its SkAlphaType set in
sync with isPremultiplied
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index ad39a57..bc2da91 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -162,7 +162,7 @@
class RecyclingPixelAllocator : public SkBitmap::Allocator {
public:
- RecyclingPixelAllocator(android::PixelRef* bitmap, unsigned int size)
+ RecyclingPixelAllocator(android::Bitmap* bitmap, unsigned int size)
: mBitmap(bitmap), mSize(size) {
}
@@ -200,7 +200,7 @@
}
private:
- android::PixelRef* const mBitmap;
+ android::Bitmap* const mBitmap;
const unsigned int mSize;
};
@@ -327,10 +327,10 @@
scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f);
}
- android::PixelRef* reuseBitmap = nullptr;
+ android::Bitmap* reuseBitmap = nullptr;
unsigned int existingBufferSize = 0;
if (javaBitmap != NULL) {
- reuseBitmap = bitmap::toPixelRef(env, javaBitmap);
+ reuseBitmap = &bitmap::toBitmap(env, javaBitmap);
if (reuseBitmap->isImmutable()) {
ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
javaBitmap = NULL;
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index 7d0915b..45bf702 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -148,10 +148,10 @@
}
// Recycle a bitmap if possible.
- android::PixelRef* recycledBitmap = nullptr;
+ android::Bitmap* recycledBitmap = nullptr;
size_t recycledBytes = 0;
if (javaBitmap) {
- recycledBitmap = bitmap::toPixelRef(env, javaBitmap);
+ recycledBitmap = &bitmap::toBitmap(env, javaBitmap);
if (recycledBitmap->isImmutable()) {
ALOGW("Warning: Reusing an immutable bitmap as an image decoder target.");
}
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 82b70fc..dffb63c 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -337,13 +337,13 @@
}
void GraphicsJNI::getSkBitmap(JNIEnv* env, jobject bitmap, SkBitmap* outBitmap) {
- android::bitmap::toPixelRef(env, bitmap)->getSkBitmap(outBitmap);
+ android::bitmap::toBitmap(env, bitmap).getSkBitmap(outBitmap);
}
-SkPixelRef* GraphicsJNI::refSkPixelRef(JNIEnv* env, jobject bitmap) {
- SkPixelRef* pixelRef = android::bitmap::toPixelRef(env, bitmap);
- pixelRef->ref();
- return pixelRef;
+SkPixelRef* GraphicsJNI::refSkPixelRef(JNIEnv* env, jobject jbitmap) {
+ android::Bitmap& bitmap = android::bitmap::toBitmap(env, jbitmap);
+ bitmap.ref();
+ return &bitmap;
}
SkColorType GraphicsJNI::getNativeBitmapColorType(JNIEnv* env, jobject jconfig) {
SkASSERT(env);
@@ -399,161 +399,9 @@
return obj;
}
-static JNIEnv* vm2env(JavaVM* vm)
-{
- JNIEnv* env = NULL;
- if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK || NULL == env)
- {
- SkDebugf("------- [%p] vm->GetEnv() failed\n", vm);
- sk_throw();
- }
- return env;
-}
-
///////////////////////////////////////////////////////////////////////////////
-static bool computeAllocationSize(const SkBitmap& bitmap, size_t* size) {
- int32_t rowBytes32 = SkToS32(bitmap.rowBytes());
- int64_t bigSize = (int64_t)bitmap.height() * rowBytes32;
- if (rowBytes32 < 0 || !sk_64_isS32(bigSize)) {
- return false; // allocation will be too large
- }
-
- *size = sk_64_asS32(bigSize);
- return true;
-}
-
-android::PixelRef* GraphicsJNI::allocateHeapPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
- const SkImageInfo& info = bitmap->info();
- if (info.colorType() == kUnknown_SkColorType) {
- LOG_ALWAYS_FATAL("unknown bitmap configuration");
- return nullptr;
- }
-
- size_t size;
- if (!computeAllocationSize(*bitmap, &size)) {
- return nullptr;
- }
-
- // we must respect the rowBytes value already set on the bitmap instead of
- // attempting to compute our own.
- const size_t rowBytes = bitmap->rowBytes();
-
- void* addr = calloc(size, 1);
- if (!addr) {
- return nullptr;
- }
-
- auto wrapper = new android::PixelRef(addr, size, info, rowBytes, ctable);
- wrapper->getSkBitmap(bitmap);
- // since we're already allocated, we lockPixels right away
- // HeapAllocator behaves this way too
- bitmap->lockPixels();
-
- return wrapper;
-}
-
-struct AndroidPixelRefContext {
- int32_t stableID;
-};
-
-static void allocatePixelsReleaseProc(void* ptr, void* ctx) {
- AndroidPixelRefContext* context = (AndroidPixelRefContext*)ctx;
- if (android::uirenderer::Caches::hasInstance()) {
- android::uirenderer::Caches::getInstance().textureCache.releaseTexture(context->stableID);
- }
-
- sk_free(ptr);
- delete context;
-}
-
-bool GraphicsJNI::allocatePixels(JNIEnv* env, SkBitmap* bitmap, SkColorTable* ctable) {
- const SkImageInfo& info = bitmap->info();
- if (info.colorType() == kUnknown_SkColorType) {
- doThrowIAE(env, "unknown bitmap configuration");
- return NULL;
- }
-
- size_t size;
- if (!computeAllocationSize(*bitmap, &size)) {
- return false;
- }
-
- // we must respect the rowBytes value already set on the bitmap instead of
- // attempting to compute our own.
- const size_t rowBytes = bitmap->rowBytes();
-
- void* addr = sk_malloc_flags(size, 0);
- if (NULL == addr) {
- return false;
- }
-
- AndroidPixelRefContext* context = new AndroidPixelRefContext;
- SkMallocPixelRef* pr = SkMallocPixelRef::NewWithProc(info, rowBytes, ctable, addr,
- &allocatePixelsReleaseProc, context);
- if (!pr) {
- delete context;
- return false;
- }
-
- // set the stableID in the context so that it can be used later in
- // allocatePixelsReleaseProc to remove the texture from the cache.
- context->stableID = pr->getStableID();
-
- bitmap->setPixelRef(pr)->unref();
- // since we're already allocated, we can lockPixels right away
- bitmap->lockPixels();
-
- return true;
-}
-
-android::PixelRef* GraphicsJNI::allocateAshmemPixelRef(JNIEnv* env, SkBitmap* bitmap,
- SkColorTable* ctable) {
- int fd;
-
- const SkImageInfo& info = bitmap->info();
- if (info.colorType() == kUnknown_SkColorType) {
- doThrowIAE(env, "unknown bitmap configuration");
- return nullptr;
- }
-
- size_t size;
- if (!computeAllocationSize(*bitmap, &size)) {
- return nullptr;
- }
-
- // we must respect the rowBytes value already set on the bitmap instead of
- // attempting to compute our own.
- const size_t rowBytes = bitmap->rowBytes();
-
- // Create new ashmem region with read/write priv
- fd = ashmem_create_region("bitmap", size);
- if (fd < 0) {
- return nullptr;
- }
-
- void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
- if (addr == MAP_FAILED) {
- close(fd);
- return nullptr;
- }
-
- if (ashmem_set_prot_region(fd, PROT_READ) < 0) {
- munmap(addr, size);
- close(fd);
- return nullptr;
- }
-
- auto wrapper = new android::PixelRef(addr, fd, size, info, rowBytes, ctable);
- wrapper->getSkBitmap(bitmap);
- // since we're already allocated, we lockPixels right away
- // HeapAllocator behaves this way too
- bitmap->lockPixels();
-
- return wrapper;
-}
-
-android::PixelRef* GraphicsJNI::mapAshmemPixelRef(JNIEnv* env, SkBitmap* bitmap,
+android::Bitmap* GraphicsJNI::mapAshmemBitmap(JNIEnv* env, SkBitmap* bitmap,
SkColorTable* ctable, int fd, void* addr, size_t size, bool readOnly) {
const SkImageInfo& info = bitmap->info();
if (info.colorType() == kUnknown_SkColorType) {
@@ -575,7 +423,7 @@
// attempting to compute our own.
const size_t rowBytes = bitmap->rowBytes();
- auto wrapper = new android::PixelRef(addr, fd, size, info, rowBytes, ctable);
+ auto wrapper = new android::Bitmap(addr, fd, size, info, rowBytes, ctable);
wrapper->getSkBitmap(bitmap);
if (readOnly) {
bitmap->pixelRef()->setImmutable();
@@ -597,14 +445,14 @@
///////////////////////////////////////////////////////////////////////////////
bool HeapAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
- mStorage.reset(GraphicsJNI::allocateHeapPixelRef(bitmap, ctable));
+ mStorage = android::Bitmap::allocateHeapBitmap(bitmap, ctable);
return !!mStorage;
}
////////////////////////////////////////////////////////////////////////////////
RecyclingClippingPixelAllocator::RecyclingClippingPixelAllocator(
- android::PixelRef* recycledBitmap, size_t recycledBytes)
+ android::Bitmap* recycledBitmap, size_t recycledBytes)
: mRecycledBitmap(recycledBitmap)
, mRecycledBytes(recycledBytes)
, mSkiaBitmap(nullptr)
@@ -702,8 +550,7 @@
}
bool AshmemPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
- JNIEnv* env = vm2env(mJavaVM);
- mStorage.reset(GraphicsJNI::allocateAshmemPixelRef(env, bitmap, ctable));
+ mStorage = android::Bitmap::allocateAshmemBitmap(bitmap, ctable);
return !!mStorage;
}
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index cf617bd..aaa8db9 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -13,7 +13,7 @@
#include "SkColorSpace.h"
#include <jni.h>
#include <hwui/Canvas.h>
-#include <hwui/PixelRef.h>
+#include <hwui/Bitmap.h>
class SkBitmapRegionDecoder;
class SkCanvas;
@@ -72,12 +72,7 @@
static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap);
- static android::PixelRef* allocateHeapPixelRef(SkBitmap* bitmap, SkColorTable* ctable);
-
- static android::PixelRef* allocateAshmemPixelRef(JNIEnv* env, SkBitmap* bitmap,
- SkColorTable* ctable);
-
- static android::PixelRef* mapAshmemPixelRef(JNIEnv* env, SkBitmap* bitmap,
+ static android::Bitmap* mapAshmemBitmap(JNIEnv* env, SkBitmap* bitmap,
SkColorTable* ctable, int fd, void* addr, size_t size, bool readOnly);
/**
@@ -109,13 +104,13 @@
/**
* Fetches the backing allocation object. Must be called!
*/
- android::PixelRef* getStorageObjAndReset() {
+ android::Bitmap* getStorageObjAndReset() {
return mStorage.release();
};
SkCodec::ZeroInitialized zeroInit() const override { return SkCodec::kYes_ZeroInitialized; }
private:
- sk_sp<android::PixelRef> mStorage;
+ sk_sp<android::Bitmap> mStorage;
};
/**
@@ -148,7 +143,7 @@
class RecyclingClippingPixelAllocator : public SkBRDAllocator {
public:
- RecyclingClippingPixelAllocator(android::PixelRef* recycledBitmap,
+ RecyclingClippingPixelAllocator(android::Bitmap* recycledBitmap,
size_t recycledBytes);
~RecyclingClippingPixelAllocator();
@@ -173,7 +168,7 @@
SkCodec::ZeroInitialized zeroInit() const override { return SkCodec::kNo_ZeroInitialized; }
private:
- android::PixelRef* mRecycledBitmap;
+ android::Bitmap* mRecycledBitmap;
const size_t mRecycledBytes;
SkBitmap* mSkiaBitmap;
bool mNeedsCopy;
@@ -184,13 +179,13 @@
explicit AshmemPixelAllocator(JNIEnv* env);
~AshmemPixelAllocator() { };
virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable);
- android::PixelRef* getStorageObjAndReset() {
+ android::Bitmap* getStorageObjAndReset() {
return mStorage.release();
};
private:
JavaVM* mJavaVM;
- sk_sp<android::PixelRef> mStorage;
+ sk_sp<android::Bitmap> mStorage;
};
diff --git a/core/jni/android/graphics/Movie.cpp b/core/jni/android/graphics/Movie.cpp
index 71988f9c..ad1b0c3 100644
--- a/core/jni/android/graphics/Movie.cpp
+++ b/core/jni/android/graphics/Movie.cpp
@@ -76,8 +76,8 @@
SkMovie* m = J2Movie(env, movie);
const SkBitmap& b = m->bitmap();
-
- c->drawBitmap(b, fx, fy, p);
+ sk_sp<android::Bitmap> wrapper = android::Bitmap::createFrom(b.info(), *b.pixelRef());
+ c->drawBitmap(*wrapper, fx, fy, p);
}
static jobject movie_decodeAsset(JNIEnv* env, jobject clazz, jlong native_asset) {
diff --git a/core/jni/android/graphics/pdf/PdfEditor.cpp b/core/jni/android/graphics/pdf/PdfEditor.cpp
index b5960dd..b142925 100644
--- a/core/jni/android/graphics/pdf/PdfEditor.cpp
+++ b/core/jni/android/graphics/pdf/PdfEditor.cpp
@@ -13,6 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#define LOG_TAG "PdfEditor"
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include <android/log.h>
+#include <utils/Log.h>
#include "PdfUtils.h"
@@ -30,11 +39,6 @@
#include "SkMatrix.h"
#include <core_jni_helpers.h>
-#include <vector>
-#include <utils/Log.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <unistd.h>
namespace android {
@@ -77,8 +81,7 @@
if (errno == EINTR) {
continue;
}
- __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
- "Error writing to buffer: %d", errno);
+ ALOGE("Error writing to buffer: %d", errno);
return false;
}
remainingBytes -= writtenByteCount;
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 776175f..04a7543 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -341,13 +341,12 @@
jlong paintHandle, jint dstDensity, jint srcDensity) {
Canvas* canvas = get_canvas(canvasHandle);
- SkBitmap skiaBitmap;
- bitmap::toSkBitmap(bitmapHandle, &skiaBitmap);
+ Bitmap& bitmap = android::bitmap::toBitmap(env, bitmapHandle);
const android::Res_png_9patch* chunk = reinterpret_cast<android::Res_png_9patch*>(chunkHandle);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
if (CC_LIKELY(dstDensity == srcDensity || dstDensity == 0 || srcDensity == 0)) {
- canvas->drawNinePatch(skiaBitmap, *chunk, left, top, right, bottom, paint);
+ canvas->drawNinePatch(bitmap, *chunk, left, top, right, bottom, paint);
} else {
canvas->save(SaveFlags::MatrixClip);
@@ -361,7 +360,7 @@
}
filteredPaint.setFilterQuality(kLow_SkFilterQuality);
- canvas->drawNinePatch(skiaBitmap, *chunk, 0, 0, (right-left)/scale, (bottom-top)/scale,
+ canvas->drawNinePatch(bitmap, *chunk, 0, 0, (right-left)/scale, (bottom-top)/scale,
&filteredPaint);
canvas->restore();
@@ -372,8 +371,7 @@
jfloat left, jfloat top, jlong paintHandle, jint canvasDensity,
jint screenDensity, jint bitmapDensity) {
Canvas* canvas = get_canvas(canvasHandle);
- SkBitmap bitmap;
- GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
+ Bitmap& bitmap = android::bitmap::toBitmap(env, jbitmap);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
if (canvasDensity == bitmapDensity || canvasDensity == 0 || bitmapDensity == 0) {
@@ -408,8 +406,7 @@
jlong matrixHandle, jlong paintHandle) {
const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- SkBitmap bitmap;
- GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
+ Bitmap& bitmap = android::bitmap::toBitmap(env, jbitmap);
get_canvas(canvasHandle)->drawBitmap(bitmap, *matrix, paint);
}
@@ -420,8 +417,7 @@
Canvas* canvas = get_canvas(canvasHandle);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- SkBitmap bitmap;
- GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
+ Bitmap& bitmap = android::bitmap::toBitmap(env, jbitmap);
if (screenDensity != 0 && screenDensity != bitmapDensity) {
Paint filteredPaint;
if (paint) {
@@ -446,7 +442,8 @@
GraphicsJNI::defaultColorSpace());
SkBitmap bitmap;
bitmap.setInfo(info);
- if (!GraphicsJNI::allocatePixels(env, &bitmap, NULL)) {
+ sk_sp<Bitmap> androidBitmap = Bitmap::allocateHeapBitmap(&bitmap, NULL);
+ if (!androidBitmap) {
return;
}
@@ -455,7 +452,7 @@
}
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- get_canvas(canvasHandle)->drawBitmap(bitmap, x, y, paint);
+ get_canvas(canvasHandle)->drawBitmap(*androidBitmap, x, y, paint);
}
static void drawBitmapMesh(JNIEnv* env, jobject, jlong canvasHandle, jobject jbitmap,
@@ -466,8 +463,7 @@
AutoJavaIntArray colorA(env, jcolors, colorIndex + ptCount);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- SkBitmap bitmap;
- GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
+ Bitmap& bitmap = android::bitmap::toBitmap(env, jbitmap);
get_canvas(canvasHandle)->drawBitmapMesh(bitmap, meshWidth, meshHeight,
vertA.ptr(), colorA.ptr(), paint);
}
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 06f22dd..aa4570f 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -291,7 +291,8 @@
whichHeap = HEAP_TTF;
is_swappable = true;
} else if ((nameLen > 4 && strstr(name, ".dex") != NULL) ||
- (nameLen > 5 && strcmp(name+nameLen-5, ".odex") == 0)) {
+ (nameLen > 5 && strcmp(name+nameLen-5, ".odex") == 0) ||
+ (nameLen > 5 && strcmp(name+nameLen-5, ".vdex") == 0)) {
whichHeap = HEAP_DEX;
is_swappable = true;
} else if (nameLen > 4 && strcmp(name+nameLen-4, ".oat") == 0) {
diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp
index 5c879b88..7387b29 100644
--- a/core/jni/android_os_HwParcel.cpp
+++ b/core/jni/android_os_HwParcel.cpp
@@ -888,15 +888,28 @@
{ "readString", "()Ljava/lang/String;",
(void *)JHwParcel_native_readString },
- { "readBoolVector", "()[Z", (void *)JHwParcel_native_readBoolVector },
- { "readInt8Vector", "()[B", (void *)JHwParcel_native_readInt8Vector },
- { "readInt16Vector", "()[S", (void *)JHwParcel_native_readInt16Vector },
- { "readInt32Vector", "()[I", (void *)JHwParcel_native_readInt32Vector },
- { "readInt64Vector", "()[J", (void *)JHwParcel_native_readInt64Vector },
- { "readFloatVector", "()[F", (void *)JHwParcel_native_readFloatVector },
- { "readDoubleVector", "()[D", (void *)JHwParcel_native_readDoubleVector },
+ { "readBoolVectorAsArray", "()[Z",
+ (void *)JHwParcel_native_readBoolVector },
- { "readStringVector", "()[Ljava/lang/String;",
+ { "readInt8VectorAsArray", "()[B",
+ (void *)JHwParcel_native_readInt8Vector },
+
+ { "readInt16VectorAsArray", "()[S",
+ (void *)JHwParcel_native_readInt16Vector },
+
+ { "readInt32VectorAsArray", "()[I",
+ (void *)JHwParcel_native_readInt32Vector },
+
+ { "readInt64VectorAsArray", "()[J",
+ (void *)JHwParcel_native_readInt64Vector },
+
+ { "readFloatVectorAsArray", "()[F",
+ (void *)JHwParcel_native_readFloatVector },
+
+ { "readDoubleVectorAsArray", "()[D",
+ (void *)JHwParcel_native_readDoubleVector },
+
+ { "readStringVectorAsArray", "()[Ljava/lang/String;",
(void *)JHwParcel_native_readStringVector },
{ "readStrongBinder", "()L" PACKAGE_PATH "/IHwBinder;",
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 6dd7a6a..a58bc90 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -97,16 +97,6 @@
// ----------------------------------------------------------------------------
-enum {
- STYLE_NUM_ENTRIES = 6,
- STYLE_TYPE = 0,
- STYLE_DATA = 1,
- STYLE_ASSET_COOKIE = 2,
- STYLE_RESOURCE_ID = 3,
- STYLE_CHANGING_CONFIGURATIONS = 4,
- STYLE_DENSITY = 5
-};
-
static jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table,
const Res_value& value, uint32_t ref, ssize_t block,
uint32_t typeSpecFlags, ResTable_config* config = NULL);
@@ -174,7 +164,7 @@
}
// Generic idmap parameters
- const char* argv[7];
+ const char* argv[8];
int argc = 0;
struct stat st;
@@ -185,10 +175,10 @@
argv[argc++] = AssetManager::TARGET_APK_PATH;
argv[argc++] = AssetManager::IDMAP_DIR;
- // Directories to scan for overlays: if OVERLAY_SKU_DIR_PROPERTY is defined,
- // use OVERLAY_DIR/<value of OVERLAY_SKU_DIR_PROPERTY> in addition to OVERLAY_DIR.
+ // Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined,
+ // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR.
char subdir[PROP_VALUE_MAX];
- int len = __system_property_get(AssetManager::OVERLAY_SKU_DIR_PROPERTY, subdir);
+ int len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PROPERTY, subdir);
if (len > 0) {
String8 overlayPath = String8(AssetManager::OVERLAY_DIR) + "/" + subdir;
if (stat(overlayPath.string(), &st) == 0) {
@@ -202,7 +192,7 @@
// Finally, invoke idmap (if any overlay directory exists)
if (argc > 5) {
execv(AssetManager::IDMAP_BIN, (char* const*)argv);
- ALOGE("failed to execl for idmap: %s", strerror(errno));
+ ALOGE("failed to execv for idmap: %s", strerror(errno));
exit(1); // should never get here
} else {
exit(0);
@@ -1170,7 +1160,7 @@
}
ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken);
- bool result = resolveAttrs(theme, defStyleAttr, defStyleRes,
+ bool result = ResolveAttrs(theme, defStyleAttr, defStyleRes,
(uint32_t*) srcValues, NSV,
(uint32_t*) src, NI,
(uint32_t*) baseDest,
@@ -1234,7 +1224,7 @@
ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken);
ResXMLParser* xmlParser = reinterpret_cast<ResXMLParser*>(xmlParserToken);
- bool result = applyStyle(theme, xmlParser,
+ bool result = ApplyStyle(theme, xmlParser,
defStyleAttr, defStyleRes,
(uint32_t*) src, NI,
(uint32_t*) baseDest,
@@ -1299,7 +1289,7 @@
}
}
- bool result = retrieveAttributes(&res, xmlParser,
+ bool result = RetrieveAttributes(&res, xmlParser,
(uint32_t*) src, NI,
(uint32_t*) baseDest,
(uint32_t*) indices);
diff --git a/core/jni/android_view_DisplayListCanvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp
index 8d2a058..74c073f 100644
--- a/core/jni/android_view_DisplayListCanvas.cpp
+++ b/core/jni/android_view_DisplayListCanvas.cpp
@@ -169,14 +169,16 @@
}
static jlong android_view_DisplayListCanvas_createDisplayListCanvas(JNIEnv* env, jobject clazz,
- jint width, jint height) {
- return reinterpret_cast<jlong>(Canvas::create_recording_canvas(width, height));
+ jlong renderNodePtr, jint width, jint height) {
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ return reinterpret_cast<jlong>(Canvas::create_recording_canvas(width, height, renderNode));
}
static void android_view_DisplayListCanvas_resetDisplayListCanvas(JNIEnv* env, jobject clazz,
- jlong canvasPtr, jint width, jint height) {
+ jlong canvasPtr, jlong renderNodePtr, jint width, jint height) {
Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
- canvas->resetRecording(width, height);
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ canvas->resetRecording(width, height, renderNode);
}
@@ -229,8 +231,8 @@
{ "nFinishRecording", "(J)J", (void*) android_view_DisplayListCanvas_finishRecording },
{ "nDrawRenderNode", "(JJ)V", (void*) android_view_DisplayListCanvas_drawRenderNode },
- { "nCreateDisplayListCanvas", "(II)J", (void*) android_view_DisplayListCanvas_createDisplayListCanvas },
- { "nResetDisplayListCanvas", "(JII)V", (void*) android_view_DisplayListCanvas_resetDisplayListCanvas },
+ { "nCreateDisplayListCanvas", "(JII)J", (void*) android_view_DisplayListCanvas_createDisplayListCanvas },
+ { "nResetDisplayListCanvas", "(JJII)V", (void*) android_view_DisplayListCanvas_resetDisplayListCanvas },
{ "nDrawLayer", "(JJ)V", (void*) android_view_DisplayListCanvas_drawLayer },
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 65f12ac..5b88181 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -186,14 +186,13 @@
return NULL;
}
- auto pixelRef = new PixelRef(
+ auto bitmap = new Bitmap(
(void*) screenshot->getPixels(), (void*) screenshot.get(), DeleteScreenshot,
screenshotInfo, rowBytes, nullptr);
screenshot.release();
- pixelRef->setImmutable();
-
- return bitmap::createBitmap(env, pixelRef,
- bitmap::kBitmapCreateFlag_Premultiplied, NULL);
+ bitmap->setImmutable();
+ return bitmap::createBitmap(env, bitmap,
+ android::bitmap::kBitmapCreateFlag_Premultiplied, NULL);
}
static void nativeScreenshot(JNIEnv* env, jclass clazz, jobject displayTokenObj,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c346849..de6f2c5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1712,11 +1712,6 @@
<permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE"
android:protectionLevel="signature|privileged|development" />
- <!-- @SystemApi @hide Allows an application to retrieve a package's importance.
- This permission is not available to third party applications. -->
- <permission android:name="android.permission.GET_PACKAGE_IMPORTANCE"
- android:protectionLevel="signature|privileged" />
-
<!-- Allows use of PendingIntent.getIntent().
@hide -->
<permission android:name="android.permission.GET_INTENT_SENDER_INTENT"
diff --git a/core/res/res/drawable-watch/scrollbar_vertical_thumb.xml b/core/res/res/drawable-watch/scrollbar_vertical_thumb.xml
new file mode 100644
index 0000000..51aced2
--- /dev/null
+++ b/core/res/res/drawable-watch/scrollbar_vertical_thumb.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="?attr/colorControlNormal"
+ android:shape="rectangle">
+ <solid android:color="#39757575" />
+ <size android:height="10dp" />
+ <corners android:radius="2dp" />
+</shape>
diff --git a/core/res/res/drawable-watch/scrollbar_vertical_track.xml b/core/res/res/drawable-watch/scrollbar_vertical_track.xml
new file mode 100644
index 0000000..5a04b1c
--- /dev/null
+++ b/core/res/res/drawable-watch/scrollbar_vertical_track.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="?attr/colorControlNormal"
+ android:shape="rectangle">
+ <solid android:color="#39ffffff" />
+ <size android:width="4dp" />
+</shape>
diff --git a/core/res/res/layout-round-watch/alert_dialog_title_material.xml b/core/res/res/layout-round-watch/alert_dialog_title_material.xml
index e543c9b..aefe28f 100644
--- a/core/res/res/layout-round-watch/alert_dialog_title_material.xml
+++ b/core/res/res/layout-round-watch/alert_dialog_title_material.xml
@@ -14,25 +14,31 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<FrameLayout
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="top|center_horizontal"
- android:minHeight="@dimen/alert_dialog_title_height">
- <ImageView android:id="@+id/icon"
+ android:orientation="vertical"
+ android:gravity="top|center_horizontal">
+ <FrameLayout
android:adjustViewBounds="true"
- android:maxHeight="24dp"
- android:maxWidth="24dp"
- android:layout_marginTop="12dp"
- android:layout_gravity="center_horizontal"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:src="@null" />
+ android:minHeight="@dimen/screen_percentage_15">
+ <ImageView android:id="@+id/icon"
+ android:adjustViewBounds="true"
+ android:maxHeight="24dp"
+ android:maxWidth="24dp"
+ android:layout_marginTop="@dimen/screen_percentage_10"
+ android:layout_marginBottom="8dp"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@null" />
+ </FrameLayout>
<TextView android:id="@+id/alertTitle"
style="?android:attr/windowTitleStyle"
- android:layout_marginTop="36dp"
android:layout_marginBottom="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
-</FrameLayout>
+</LinearLayout>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 09b55d9..1e5f6a1 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1570,7 +1570,7 @@
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Supprimé par votre administrateur"</string>
<string name="battery_saver_description" msgid="1960431123816253034">"Pour améliorer l\'autonomie de la batterie, l\'économiseur de batterie réduit les performances et désactive le vibreur, les services de localisation et la plupart des données en arrière-plan. Les messageries électroniques ou autres applications utilisant la synchronisation pourraient ne pas se mettre à jour, sauf si vous les ouvrez.\n\nL\'économiseur de batterie s\'éteint automatiquement lorsque l\'appareil est en charge."</string>
<string name="data_saver_description" msgid="6015391409098303235">"Pour réduire la consommation des données, l\'économiseur de données empêche certaines applications d\'envoyer ou de recevoir des données en arrière-plan. Ainsi, une application que vous utilisez actuellement peut accéder à des données, mais moins souvent. Par exemple, il se peut que les images ne s\'affichent pas tant que vous n\'appuyez pas dessus."</string>
- <string name="data_saver_enable_title" msgid="4674073932722787417">"Activer sauvegarde données ?"</string>
+ <string name="data_saver_enable_title" msgid="4674073932722787417">"Activer l\'économiseur de données ?"</string>
<string name="data_saver_enable_button" msgid="7147735965247211818">"Activer"</string>
<plurals name="zen_mode_duration_minutes_summary" formatted="false" msgid="4367877408072000848">
<item quantity="one">Pendant %1$d minute (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml
index 7cdc9bb..5a1ad70 100644
--- a/core/res/res/values-hy-rAM/strings.xml
+++ b/core/res/res/values-hy-rAM/strings.xml
@@ -214,7 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Հեռախոսի ընտրանքներ"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Էկրանի փական"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Անջատել"</string>
- <string name="global_action_emergency" msgid="7112311161137421166">"Արտակարգ իրավիճակ"</string>
+ <string name="global_action_emergency" msgid="7112311161137421166">"Շտապ կանչ"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Վրիպակի զեկույց"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Գրել սխալի զեկույց"</string>
<string name="bugreport_message" msgid="398447048750350456">"Սա տեղեկություններ կհավաքագրի ձեր սարքի առկա կարգավիճակի մասին և կուղարկի այն էլեկտրոնային նամակով: Որոշակի ժամանակ կպահանջվի վրիպակի մասին զեկուցելու պահից սկսած մինչ ուղարկելը: Խնդրում ենք փոքր-ինչ համբերատար լինել:"</string>
@@ -250,7 +250,7 @@
<string name="permgrouplab_calendar" msgid="5863508437783683902">"Օրացույց"</string>
<string name="permgroupdesc_calendar" msgid="3889615280211184106">"օգտագործել օրացույցը"</string>
<string name="permgrouplab_sms" msgid="228308803364967808">"Կարճ հաղորդագրություն"</string>
- <string name="permgroupdesc_sms" msgid="4656988620100940350">"ուղարկել և դիտել SMS հաղորդ․-ները"</string>
+ <string name="permgroupdesc_sms" msgid="4656988620100940350">"ուղարկել և դիտել SMS-ները"</string>
<string name="permgrouplab_storage" msgid="1971118770546336966">"Պահոց"</string>
<string name="permgroupdesc_storage" msgid="637758554581589203">"օգտագործել լուսանկարները, մեդիա ֆայլերը և ձեր սարքում պահվող մյուս ֆայլերը"</string>
<string name="permgrouplab_microphone" msgid="171539900250043464">"Բարձրախոս"</string>
@@ -674,7 +674,7 @@
<string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Ապակողպելու կամ շտապ կանչ անելու համար սեղմեք «Ընտրացանկ»"</string>
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Ապակողպելու համար սեղմեք Ցանկը:"</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Հավաքեք սխեման` ապակողպելու համար"</string>
- <string name="lockscreen_emergency_call" msgid="5298642613417801888">"Արտակարգ իրավիճակ"</string>
+ <string name="lockscreen_emergency_call" msgid="5298642613417801888">"Շտապ կանչ"</string>
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Վերադառնալ զանգին"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Ճիշտ է:"</string>
<string name="lockscreen_pattern_wrong" msgid="4317955014948108794">"Կրկին փորձեք"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index d655477..05fe8d0 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -254,7 +254,7 @@
<string name="permgrouplab_storage" msgid="1971118770546336966">"Penyimpanan"</string>
<string name="permgroupdesc_storage" msgid="637758554581589203">"mengakses foto, media, dan file di perangkat"</string>
<string name="permgrouplab_microphone" msgid="171539900250043464">"Mikrofon"</string>
- <string name="permgroupdesc_microphone" msgid="4988812113943554584">"rekam audio"</string>
+ <string name="permgroupdesc_microphone" msgid="4988812113943554584">"merekam audio"</string>
<string name="permgrouplab_camera" msgid="4820372495894586615">"Kamera"</string>
<string name="permgroupdesc_camera" msgid="3250611594678347720">"mengambil gambar dan merekam video"</string>
<string name="permgrouplab_phone" msgid="5229115638567440675">"Telepon"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 5305b1f..02736ec 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -512,8 +512,8 @@
<string name="permdesc_bindCarrierMessagingService" msgid="2762882888502113944">"携帯通信会社のSMSサービスのトップレベルインターフェースにバインドすることを所有者に許可します。通常のアプリでは不要です。"</string>
<string name="permlab_bindCarrierServices" msgid="3233108656245526783">"携帯通信会社のサービスへのバインド"</string>
<string name="permdesc_bindCarrierServices" msgid="1391552602551084192">"携帯通信会社のサービスにバインドすることを所有者に許可します。通常のアプリでは不要です。"</string>
- <string name="permlab_access_notification_policy" msgid="4247510821662059671">"[通知を非表示]へのアクセス"</string>
- <string name="permdesc_access_notification_policy" msgid="3296832375218749580">"[通知を非表示]の設定の読み取りと書き込みをアプリに許可します。"</string>
+ <string name="permlab_access_notification_policy" msgid="4247510821662059671">"マナーモードへのアクセス"</string>
+ <string name="permdesc_access_notification_policy" msgid="3296832375218749580">"マナーモード設定の読み取りと書き込みをアプリに許可します。"</string>
<string name="policylab_limitPassword" msgid="4497420728857585791">"パスワードルールの設定"</string>
<string name="policydesc_limitPassword" msgid="2502021457917874968">"画面ロックのパスワードとPINの長さと使用できる文字を制御します。"</string>
<string name="policylab_watchLogin" msgid="914130646942199503">"画面ロック解除試行の監視"</string>
@@ -1607,10 +1607,10 @@
<string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>まで"</string>
<string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>(次のアラーム)まで"</string>
<string name="zen_mode_forever" msgid="7420011936770086993">"ユーザーがOFFにするまで"</string>
- <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"[通知を非表示]をOFFにするまで"</string>
+ <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"マナーモードを OFF にするまで"</string>
<string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
<string name="toolbar_collapse_description" msgid="2821479483960330739">"折りたたむ"</string>
- <string name="zen_mode_feature_name" msgid="5254089399895895004">"通知を非表示"</string>
+ <string name="zen_mode_feature_name" msgid="5254089399895895004">"マナーモード"</string>
<string name="zen_mode_downtime_feature_name" msgid="2626974636779860146">"ダウンタイム"</string>
<string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"平日の夜"</string>
<string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"週末"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 4191865..f0cfd0b 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -260,7 +260,7 @@
<string name="permgrouplab_phone" msgid="5229115638567440675">"전화"</string>
<string name="permgroupdesc_phone" msgid="6234224354060641055">"통화 상태를 관리하거나 전화를 걸 수 있도록"</string>
<string name="permgrouplab_sensors" msgid="416037179223226722">"신체 센서"</string>
- <string name="permgroupdesc_sensors" msgid="7147968539346634043">"생체 신호에 관한 센서 데이터에 접근할 수 있도록"</string>
+ <string name="permgroupdesc_sensors" msgid="7147968539346634043">"생체 신호에 관한 센서 데이터에 액세스"</string>
<string name="capability_title_canRetrieveWindowContent" msgid="3901717936930170320">"창 콘텐츠 가져오기"</string>
<string name="capability_desc_canRetrieveWindowContent" msgid="3772225008605310672">"상호작용 중인 창의 콘텐츠를 검사합니다."</string>
<string name="capability_title_canRequestTouchExploration" msgid="3108723364676667320">"터치하여 탐색 사용"</string>
diff --git a/core/res/res/values-ldrtl-television/config.xml b/core/res/res/values-ldrtl-television/config.xml
index e237acc..503b902 100644
--- a/core/res/res/values-ldrtl-television/config.xml
+++ b/core/res/res/values-ldrtl-television/config.xml
@@ -21,7 +21,8 @@
for TV products. Do not translate. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Default bounds [left top right bottom] on screen for picture-in-picture windows. -->
- <string translatable="false" name="config_defaultPictureInPictureBounds">"112 54 592 324"</string>
+ <!-- The default gravity for the picture-in-picture window.
+ Currently, this maps to Gravity.TOP | Gravity.LEFT -->
+ <integer name="config_defaultPictureInPictureGravity">0x33</integer>
</resources>
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index c0716e9..c27cb06 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -24,6 +24,15 @@
<!-- Flags enabling default window features. See Window.java -->
<bool name="config_defaultWindowFeatureOptionsPanel">false</bool>
- <!-- Default bounds [left top right bottom] on screen for picture-in-picture windows. -->
- <string translatable="false" name="config_defaultPictureInPictureBounds">"1328 54 1808 324"</string>
+ <!-- Max default size [WIDTHxHEIGHT] on screen for picture-in-picture windows to fit inside.
+ These values are in DPs and will be converted to pixel sizes internally. -->
+ <string translatable="false" name="config_defaultPictureInPictureSize">240x135</string>
+
+ <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
+ These values are in DPs and will be converted to pixel sizes internally. -->
+ <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">56x27</string>
+
+ <!-- The default gravity for the picture-in-picture window.
+ Currently, this maps to Gravity.TOP | Gravity.RIGHT -->
+ <integer name="config_defaultPictureInPictureGravity">0x35</integer>
</resources>
diff --git a/core/res/res/values-watch/colors_device_defaults.xml b/core/res/res/values-watch/colors_device_defaults.xml
index 9150cc4..15786b4 100644
--- a/core/res/res/values-watch/colors_device_defaults.xml
+++ b/core/res/res/values-watch/colors_device_defaults.xml
@@ -18,4 +18,7 @@
overlaying new theme colors. -->
<resources>
<color name="button_normal_device_default_dark">@color/btn_default_material_dark</color>
+ <!-- Use the same value as for accent_device_default_dark but start with #99,
+ i.e. 60% opacity -->
+ <color name="accent_device_default_dark_60_percent_opacity">#995E97f6</color>
</resources>
diff --git a/core/res/res/values-watch/config_material.xml b/core/res/res/values-watch/config_material.xml
index 104d122..529f18b 100644
--- a/core/res/res/values-watch/config_material.xml
+++ b/core/res/res/values-watch/config_material.xml
@@ -29,4 +29,8 @@
<!-- Always overscan by default to ensure onApplyWindowInsets will always be called. -->
<bool name="config_windowOverscanByDefault">true</bool>
+
+ <!-- Style the scrollbars accoridngly. -->
+ <drawable name="config_scrollbarThumbVertical">@drawable/scrollbar_vertical_thumb</drawable>
+ <drawable name="config_scrollbarTrackVertical">@drawable/scrollbar_vertical_track</drawable>
</resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index b67d6e2..68e87c2 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -195,7 +195,7 @@
<string name="silent_mode_ring" msgid="8592241816194074353">"振铃器开启"</string>
<string name="reboot_to_update_title" msgid="6212636802536823850">"Android 系统更新"</string>
<string name="reboot_to_update_prepare" msgid="6305853831955310890">"正在准备更新…"</string>
- <string name="reboot_to_update_package" msgid="3871302324500927291">"正在处理更新文件包…"</string>
+ <string name="reboot_to_update_package" msgid="3871302324500927291">"正在处理更新软件包…"</string>
<string name="reboot_to_update_reboot" msgid="6428441000951565185">"正在重新启动…"</string>
<string name="reboot_to_reset_title" msgid="4142355915340627490">"恢复出厂设置"</string>
<string name="reboot_to_reset_message" msgid="2432077491101416345">"正在重新启动…"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 092109b..5df33bd 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2472,8 +2472,17 @@
-->
<integer name="config_navBarOpacityMode">0</integer>
- <!-- Default bounds [left top right bottom] on screen for picture-in-picture windows. -->
- <string translatable="false" name="config_defaultPictureInPictureBounds">"0 0 100 100"</string>
+ <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
+ These values are in DPs and will be converted to pixel sizes internally. -->
+ <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">10x10</string>
+
+ <!-- Max default size [WIDTHxHEIGHT] on screen for picture-in-picture windows to fit inside.
+ These values are in DPs and will be converted to pixel sizes internally. -->
+ <string translatable="false" name="config_defaultPictureInPictureSize">216x135</string>
+
+ <!-- The default gravity for the picture-in-picture window.
+ Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
+ <integer name="config_defaultPictureInPictureGravity">0x55</integer>
<!-- Controls the snap mode for the docked stack divider
0 - 3 snap targets: left/top has 16:9 ratio, 1:1, and right/bottom has 16:9 ratio
diff --git a/core/res/res/values/config_material.xml b/core/res/res/values/config_material.xml
index 29494db..840a551 100644
--- a/core/res/res/values/config_material.xml
+++ b/core/res/res/values/config_material.xml
@@ -37,4 +37,8 @@
<!-- The amount to offset when scrolling to a selection in an AlertDialog -->
<dimen name="config_alertDialogSelectionScrollOffset">0dp</dimen>
+
+ <!-- Style the scrollbars accoridngly. -->
+ <drawable name="config_scrollbarThumbVertical">@drawable/scrollbar_handle_material</drawable>
+ <drawable name="config_scrollbarTrackVertical">@null</drawable>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ad3e204..48cea84 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -310,7 +310,9 @@
<java-symbol type="bool" name="config_guestUserEphemeral" />
<java-symbol type="bool" name="config_localDisplaysMirrorContent" />
<java-symbol type="bool" name="config_enableAppWidgetService" />
- <java-symbol type="string" name="config_defaultPictureInPictureBounds" />
+ <java-symbol type="string" name="config_defaultPictureInPictureScreenEdgeInsets" />
+ <java-symbol type="string" name="config_defaultPictureInPictureSize" />
+ <java-symbol type="integer" name="config_defaultPictureInPictureGravity" />
<java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_threshold" />
<java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_factor" />
<java-symbol type="integer" name="config_wifi_framework_5GHz_preference_penalty_threshold" />
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 0eb4c8d..ff8693b 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -212,9 +212,9 @@
<item name="scrollbarDefaultDelayBeforeFade">400</item>
<item name="scrollbarSize">10dp</item>
<item name="scrollbarThumbHorizontal">@drawable/scrollbar_handle_material</item>
- <item name="scrollbarThumbVertical">@drawable/scrollbar_handle_material</item>
+ <item name="scrollbarThumbVertical">@drawable/config_scrollbarThumbVertical</item>
<item name="scrollbarTrackHorizontal">@null</item>
- <item name="scrollbarTrackVertical">@null</item>
+ <item name="scrollbarTrackVertical">@drawable/config_scrollbarTrackVertical</item>
<!-- Text selection handle attributes -->
<item name="textSelectHandleLeft">@drawable/text_select_handle_left_material</item>
@@ -573,9 +573,9 @@
<item name="scrollbarDefaultDelayBeforeFade">400</item>
<item name="scrollbarSize">10dp</item>
<item name="scrollbarThumbHorizontal">@drawable/scrollbar_handle_material</item>
- <item name="scrollbarThumbVertical">@drawable/scrollbar_handle_material</item>
+ <item name="scrollbarThumbVertical">@drawable/config_scrollbarThumbVertical</item>
<item name="scrollbarTrackHorizontal">@null</item>
- <item name="scrollbarTrackVertical">@null</item>
+ <item name="scrollbarTrackVertical">@drawable/config_scrollbarTrackVertical</item>
<!-- Text selection handle attributes -->
<item name="textSelectHandleLeft">@drawable/text_select_handle_left_material</item>
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
new file mode 100644
index 0000000..e9e3a18
--- /dev/null
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2016 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 android.app.admin;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/** Unit tests for {@link PasswordMetrics}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PasswordMetricsTest {
+
+ @Test
+ public void testIsDefault() {
+ final PasswordMetrics metrics = new PasswordMetrics();
+ assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, metrics.quality);
+ assertEquals(0, metrics.length);
+ assertEquals(0, metrics.letters);
+ assertEquals(0, metrics.upperCase);
+ assertEquals(0, metrics.lowerCase);
+ assertEquals(0, metrics.numeric);
+ assertEquals(0, metrics.symbols);
+ assertEquals(0, metrics.nonLetter);
+ assertTrue("default constructor does not produce default metrics", metrics.isDefault());
+ }
+
+ @Test
+ public void testIsNotDefault() {
+ final PasswordMetrics metrics = new PasswordMetrics(
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, 12);
+ assertFalse("non-default metrics are repoted as default", metrics.isDefault());
+ }
+
+ @Test
+ public void testComputeForEmptyPassword() {
+ final PasswordMetrics metrics = PasswordMetrics.computeForPassword("");
+ assertTrue("empty password has default metrics", metrics.isDefault());
+ }
+
+ @Test
+ public void testParceling() {
+ final int quality = 0;
+ final int length = 1;
+ final int letters = 2;
+ final int upperCase = 3;
+ final int lowerCase = 4;
+ final int numeric = 5;
+ final int symbols = 6;
+ final int nonLetter = 7;
+
+ final Parcel parcel = Parcel.obtain();
+ final PasswordMetrics metrics;
+ try {
+ new PasswordMetrics(
+ quality, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter)
+ .writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ metrics = PasswordMetrics.CREATOR.createFromParcel(parcel);
+ } finally {
+ parcel.recycle();
+ }
+
+ assertEquals(quality, metrics.quality);
+ assertEquals(length, metrics.length);
+ assertEquals(letters, metrics.letters);
+ assertEquals(upperCase, metrics.upperCase);
+ assertEquals(lowerCase, metrics.lowerCase);
+ assertEquals(numeric, metrics.numeric);
+ assertEquals(symbols, metrics.symbols);
+ assertEquals(nonLetter, metrics.nonLetter);
+
+ }
+
+ @Test
+ public void testComputeForPassword_metrics() {
+ final PasswordMetrics metrics = PasswordMetrics.computeForPassword("6B~0z1Z3*8A");
+ assertEquals(11, metrics.length);
+ assertEquals(4, metrics.letters);
+ assertEquals(3, metrics.upperCase);
+ assertEquals(1, metrics.lowerCase);
+ assertEquals(5, metrics.numeric);
+ assertEquals(2, metrics.symbols);
+ assertEquals(7, metrics.nonLetter);
+ }
+
+ @Test
+ public void testComputeForPassword_quality() {
+ assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC,
+ PasswordMetrics.computeForPassword("a1").quality);
+ assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC,
+ PasswordMetrics.computeForPassword("a").quality);
+ assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC,
+ PasswordMetrics.computeForPassword("*~&%$").quality);
+ assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
+ PasswordMetrics.computeForPassword("1").quality);
+ // contains a long sequence so isn't complex
+ assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
+ PasswordMetrics.computeForPassword("1234").quality);
+ assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ PasswordMetrics.computeForPassword("").quality);
+ }
+
+ @Test
+ public void testMaxLengthSequence() {
+ assertEquals(4, PasswordMetrics.maxLengthSequence("1234"));
+ assertEquals(5, PasswordMetrics.maxLengthSequence("13579"));
+ assertEquals(4, PasswordMetrics.maxLengthSequence("1234abd"));
+ assertEquals(3, PasswordMetrics.maxLengthSequence("aabc"));
+ assertEquals(1, PasswordMetrics.maxLengthSequence("qwertyuio"));
+ assertEquals(3, PasswordMetrics.maxLengthSequence("@ABC"));
+ // anything that repeats
+ assertEquals(4, PasswordMetrics.maxLengthSequence(";;;;"));
+ // ordered, but not composed of alphas or digits
+ assertEquals(1, PasswordMetrics.maxLengthSequence(":;<=>"));
+ }
+}
diff --git a/core/tests/coretests/src/android/util/TokenBucketTest.java b/core/tests/coretests/src/android/util/TokenBucketTest.java
new file mode 100644
index 0000000..f7ac20c
--- /dev/null
+++ b/core/tests/coretests/src/android/util/TokenBucketTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2016 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.util;
+
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+import junit.framework.TestCase;
+
+public class TokenBucketTest extends TestCase {
+
+ static final int FILL_DELTA_VERY_SHORT = 1;
+ static final int FILL_DELTA_VERY_LONG = Integer.MAX_VALUE;
+
+ public void testArgumentValidation() {
+ assertThrow(() -> new TokenBucket(0, 1, 1));
+ assertThrow(() -> new TokenBucket(1, 0, 1));
+ assertThrow(() -> new TokenBucket(1, 1, 0));
+ assertThrow(() -> new TokenBucket(0, 1));
+ assertThrow(() -> new TokenBucket(1, 0));
+ assertThrow(() -> new TokenBucket(-1, 1, 1));
+ assertThrow(() -> new TokenBucket(1, -1, 1));
+ assertThrow(() -> new TokenBucket(1, 1, -1));
+ assertThrow(() -> new TokenBucket(-1, 1));
+ assertThrow(() -> new TokenBucket(1, -1));
+
+ new TokenBucket(1000, 100, 0);
+ new TokenBucket(1000, 100, 10);
+ new TokenBucket(5000, 50);
+ new TokenBucket(5000, 1);
+ }
+
+ public void testInitialCapacity() {
+ drain(new TokenBucket(FILL_DELTA_VERY_LONG, 1), 1);
+ drain(new TokenBucket(FILL_DELTA_VERY_LONG, 10), 10);
+ drain(new TokenBucket(FILL_DELTA_VERY_LONG, 1000), 1000);
+
+ drain(new TokenBucket(FILL_DELTA_VERY_LONG, 10, 0), 0);
+ drain(new TokenBucket(FILL_DELTA_VERY_LONG, 10, 3), 3);
+ drain(new TokenBucket(FILL_DELTA_VERY_LONG, 10, 10), 10);
+
+ drain(new TokenBucket(FILL_DELTA_VERY_LONG, 10, 100), 10);
+
+ drain(new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50), 50);
+ drain(new TokenBucket((int)DateUtils.HOUR_IN_MILLIS, 10), 10);
+ drain(new TokenBucket((int)DateUtils.DAY_IN_MILLIS, 200), 200);
+ }
+
+ public void testReset() {
+ TokenBucket tb = new TokenBucket(FILL_DELTA_VERY_LONG, 100, 10);
+ drain(tb, 10);
+
+ tb.reset(50);
+ drain(tb, 50);
+
+ tb.reset(50);
+ getOneByOne(tb, 10);
+ assertTrue(tb.has());
+
+ tb.reset(30);
+ drain(tb, 30);
+ }
+
+ public void testFill() throws Exception {
+ int delta = 50;
+ TokenBucket tb = new TokenBucket(delta, 10, 0);
+
+ assertEmpty(tb);
+
+ Thread.sleep(3 * delta / 2);
+
+ assertTrue(tb.has());
+ }
+
+ public void testRefill() throws Exception {
+ TokenBucket tb = new TokenBucket(FILL_DELTA_VERY_SHORT, 10, 10);
+
+ assertEquals(5, tb.get(5));
+ assertEquals(5, tb.get(5));
+
+ while (tb.available() < 10) {
+ Thread.sleep(2);
+ }
+
+ assertEquals(10, tb.get(10));
+
+ while (tb.available() < 10) {
+ Thread.sleep(2);
+ }
+
+ assertEquals(10, tb.get(100));
+ }
+
+ public void testAverage() throws Exception {
+ final int delta = 3;
+ final int want = 60;
+
+ long start = SystemClock.elapsedRealtime();
+ TokenBucket tb = new TokenBucket(delta, 20, 0);
+
+ for (int i = 0; i < want; i++) {
+ while (!tb.has()) {
+ Thread.sleep(5 * delta);
+ }
+ tb.get();
+ }
+
+ assertDuration(want * delta, SystemClock.elapsedRealtime() - start);
+ }
+
+ public void testBurst() throws Exception {
+ final int delta = 2;
+ final int capacity = 20;
+ final int want = 100;
+
+ long start = SystemClock.elapsedRealtime();
+ TokenBucket tb = new TokenBucket(delta, capacity, 0);
+
+ int total = 0;
+ while (total < want) {
+ while (!tb.has()) {
+ Thread.sleep(capacity * delta - 2);
+ }
+ total += tb.get(tb.available());
+ }
+
+ assertDuration(total * delta, SystemClock.elapsedRealtime() - start);
+ }
+
+ static void getOneByOne(TokenBucket tb, int n) {
+ while (n > 0) {
+ assertTrue(tb.has());
+ assertTrue(tb.available() >= n);
+ assertTrue(tb.get());
+ assertTrue(tb.available() >= n - 1);
+ n--;
+ }
+ }
+
+ void assertEmpty(TokenBucket tb) {
+ assertFalse(tb.has());
+ assertEquals(0, tb.available());
+ assertFalse(tb.get());
+ }
+
+ void drain(TokenBucket tb, int n) {
+ getOneByOne(tb, n);
+ assertEmpty(tb);
+ }
+
+ void assertDuration(long expected, long elapsed) {
+ String msg = String.format(
+ "expected elapsed time at least %d ms, but was %d ms", expected, elapsed);
+ elapsed += 1; // one millisecond extra guard
+ assertTrue(msg, elapsed >= expected);
+ }
+
+ void assertThrow(Fn fn) {
+ try {
+ fn.call();
+ fail("expected n exception to be thrown.");
+ } catch (Throwable t) {}
+ }
+
+ interface Fn { void call(); }
+}
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index ea22cd1..d0802a0 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -170,6 +170,21 @@
}
@SmallTest
+ public void testLongPressAndDragToSelect_emoji() throws Exception {
+ final String text = "\uD83D\uDE00\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(replaceText(text));
+
+ onView(withId(R.id.textview)).perform(longPressAndDragOnText(4, 6));
+ onView(withId(R.id.textview)).check(hasSelection("\uD83D\uDE02"));
+
+ onView(withId(R.id.textview)).perform(click());
+
+ onView(withId(R.id.textview)).perform(longPressAndDragOnText(4, 2));
+ onView(withId(R.id.textview)).check(hasSelection("\uD83D\uDE01"));
+ }
+
+ @SmallTest
public void testDragAndDrop() throws Exception {
final String text = "abc def ghi.";
onView(withId(R.id.textview)).perform(click());
diff --git a/docs/html/_redirects.yaml b/docs/html/_redirects.yaml
index 6185dab..7daf85c 100644
--- a/docs/html/_redirects.yaml
+++ b/docs/html/_redirects.yaml
@@ -1237,7 +1237,7 @@
- from: /r/studio-ui/run-with-work-profile.html
to: /studio/run/index.html?utm_source=android-studio#ir-work-profile
- from: /r/studio-ui/am-gpu-debugger.html
- to: /studio/profile/am-gpu.html?utm_source=android-studio
+ to: /studio/debug/am-gpu-debugger.html?utm_source=android-studio
- from: /r/studio-ui/theme-editor.html
to: /studio/write/theme-editor.html?utm_source=android-studio
- from: /r/studio-ui/translations-editor.html
diff --git a/docs/html/google/play/billing/billing_integrate.jd b/docs/html/google/play/billing/billing_integrate.jd
index 5d6b3a8..506a440 100755
--- a/docs/html/google/play/billing/billing_integrate.jd
+++ b/docs/html/google/play/billing/billing_integrate.jd
@@ -9,18 +9,18 @@
<h2>In this document</h2>
<ol>
<li><a href="#billing-add-aidl">Adding the AIDL file</a></li>
- <li><a href="#billing-permission">Updating Your Manifest</a></li>
+ <li><a href="#billing-permission">Updating your manifest</a></li>
<li><a href="#billing-service">Creating a ServiceConnection</a></li>
- <li><a href="#billing-requests">Making In-app Billing Requests</a>
+ <li><a href="#billing-requests">Making In-app Billing requests</a>
<ol>
- <li><a href="#QueryDetails">Querying Items Available for Purchase</a><li>
- <li><a href="#Purchase">Purchasing an Item</a></li>
- <li><a href="#QueryPurchases">Querying Purchased Items</a></li>
- <li><a href="#Consume">Consuming a Purchase</a></li>
- <li><a href="#Subs">Implementing Subscriptions</a></li>
+ <li><a href="#QueryDetails">Querying items available for purchase</a><li>
+ <li><a href="#Purchase">Purchasing an item</a></li>
+ <li><a href="#QueryPurchases">Querying purchased items</a></li>
+ <li><a href="#Consume">Consuming a purchase</a></li>
+ <li><a href="#Subs">Implementing subscriptions</a></li>
</ol>
</li>
- <li><a href="#billing-security">Securing Your App</a>
+ <li><a href="#billing-security">Securing your app</a>
</ol>
<h2>Reference</h2>
<ol>
@@ -42,7 +42,7 @@
In-app Billing on Google Play provides a straightforward, simple interface
for sending In-app Billing requests and managing In-app Billing transactions
using Google Play. The information below covers the basics of how to make
- calls from your application to the In-app Billing service using the Version 3
+ calls from your application to the In-app Billing service using the In-app Billing Version 3
API.
</p>
@@ -51,26 +51,25 @@
your application, see the <a href=
"{@docRoot}training/in-app-billing/index.html">Selling In-app Products</a>
training class. The training class provides a complete sample In-app Billing
- application, including convenience classes to handle key tasks related to
- setting up your connection, sending billing requests and processing responses
+ application, including convenience classes to handle key tasks that are related to
+ setting up your connection, sending billing requests, processing responses
from Google Play, and managing background threading so that you can make
In-app Billing calls from your main activity.
</p>
<p>
- Before you start, be sure that you read the <a href=
+ Before you start, read the <a href=
"{@docRoot}google/play/billing/billing_overview.html">In-app Billing
- Overview</a> to familiarize yourself with concepts that will make it easier
+ Overview</a> to familiarize yourself with concepts that make it easier
for you to implement In-app Billing.
</p>
-<p>To implement In-app Billing in your application, you need to do the
-following:</p>
+<p>Complete these steps to implement In-app Billing in your application:</p>
<ol>
<li>Add the In-app Billing library to your project.</li>
<li>Update your {@code AndroidManifest.xml} file.</li>
- <li>Create a {@code ServiceConnection} and bind it to
+ <li>Create a {@code ServiceConnection} and bind it to the
{@code IInAppBillingService}.</li>
<li>Send In-app Billing requests from your application to
{@code IInAppBillingService}.</li>
@@ -79,55 +78,56 @@
<h2 id="billing-add-aidl">Adding the AIDL file to your project</h2>
-<p>{@code IInAppBillingService.aidl} is an Android Interface Definition
+<p>The {@code IInAppBillingService.aidl} is an Android Interface Definition
Language (AIDL) file that defines the interface to the In-app Billing Version
-3 service. You will use this interface to make billing requests by invoking IPC
+3 service. You can use this interface to make billing requests by invoking IPC
method calls.</p>
-<p>To get the AIDL file:</p>
+
+<p>Complete these steps to get the AIDL file:</p>
<ol>
<li>Open the <a href="{@docRoot}tools/help/sdk-manager.html">Android SDK Manager</a>.</li>
<li>In the SDK Manager, expand the {@code Extras} section.</li>
<li>Select <strong>Google Play Billing Library</strong>.</li>
<li>Click <strong>Install packages</strong> to complete the download.</li>
</ol>
-<p>The {@code IInAppBillingService.aidl} file will be installed to {@code <sdk>/extras/google/play_billing/}.</p>
+<p>The {@code IInAppBillingService.aidl} file will be installed to {@code <sdk>/extras/google/play_billing/}.</p>
-<p>To add the AIDL to your project:</p>
+<p>Complete these steps to add the AIDL to your project:</p>
<ol>
- <li>First, download the Google Play Billing Library to your Android project:
+ <li>Download the Google Play Billing Library to your Android project:
<ol type="a">
<li>Select <strong>Tools > Android > SDK Manager</strong>.</li>
<li>Under <strong>Appearance & Behavior > System Settings > Android SDK</strong>,
select the <em>SDK Tools</em> tab to select and download <em>Google Play Billing
Library</em>.</li></ol>
- <li>Next, copy the {@code IInAppBillingService.aidl} file to your project.
+ <li>Copy the {@code IInAppBillingService.aidl} file to your project.
<ul>
- <li>If you are using Android Studio:
+ <li>If you are using Android Studio, complete these steps to copy the file:
<ol type="a">
<li>Navigate to {@code src/main} in the Project tool window.</li>
- <li>Select <strong>File > New > Directory</strong> and enter {@code aidl} in the
- <em>New Directory</em> window, then select <strong>OK</strong>.
+ <li>Select <strong>File > New > Directory</strong>, enter {@code aidl} in the
+ <em>New Directory</em> window, and select <strong>OK</strong>.
- <li>Select <strong>File > New > Package</strong> and enter
- {@code com.android.vending.billing} in the <em>New Package</em> window, then select
+ <li>Select <strong>File > New > Package</strong>, enter
+ {@code com.android.vending.billing} in the <em>New Package</em> window, and select
<strong>OK</strong>.</li>
<li>Using your operating system file explorer, navigate to
- {@code <sdk>/extras/google/play_billing/}, copy the
+ {@code <sdk>/extras/google/play_billing/}, copy the
{@code IInAppBillingService.aidl} file, and paste it into the
{@code com.android.vending.billing} package in your project.
</li>
</ol>
</li>
- <li>If you are developing in a non-Android Studio environment: Create the
- following directory {@code /src/com/android/vending/billing} and copy the
- {@code IInAppBillingService.aidl} file into this directory. Put the AIDL
- file into your project and use the Gradle tool to build your project so that
- the <code>IInAppBillingService.java</code> file gets generated.
+ <li>If you are developing in a non-Android Studio environment, create the
+ following directory: {@code /src/com/android/vending/billing}. Copy the
+ {@code IInAppBillingService.aidl} file into this directory. Place the AIDL
+ file in your project and use the Gradle tool to build your project so that
+ the <code>IInAppBillingService.java</code> file is generated.
</li>
</ul>
</li>
@@ -137,16 +137,16 @@
</li>
</ol>
-<h2 id="billing-permission">Updating Your App's Manifest</h2>
+<h2 id="billing-permission">Updating your app's manifest</h2>
<p>
In-app billing relies on the Google Play application, which handles all
- communication between your application and the Google Play server. To use the
+ of the communication between your application and the Google Play server. To use the
Google Play application, your application must request the proper permission.
You can do this by adding the {@code com.android.vending.BILLING} permission
to your AndroidManifest.xml file. If your application does not declare the
In-app Billing permission, but attempts to send billing requests, Google Play
- will refuse the requests and respond with an error.
+ refuses the requests and responds with an error.
</p>
<p>
@@ -182,7 +182,7 @@
onServiceDisconnected} and {@link
android.content.ServiceConnection#onServiceConnected onServiceConnected}
methods to get a reference to the {@code IInAppBillingService} instance after
- a connection has been established.
+ a connection is established.
</p>
<pre>
@@ -208,20 +208,25 @@
bindService} method. Pass the method an {@link android.content.Intent} that
references the In-app Billing service and an instance of the {@link
android.content.ServiceConnection} that you created, and explicitly set the
- Intent's target package name to <code>com.android.vending</code> — the
+ Intent's target package name to <code>com.android.vending</code>—the
package name of Google Play app.
</p>
<p class="caution">
<strong>Caution:</strong> To protect the security of billing transactions,
- always make sure to explicitly set the intent's target package name to
+ always explicitly set the intent's target package name to
<code>com.android.vending</code>, using {@link
- android.content.Intent#setPackage(java.lang.String) setPackage()} as shown in
- the example below. Setting the package name explicitly ensures that
+ android.content.Intent#setPackage(java.lang.String) setPackage()}.
+ Setting the package name explicitly ensures that
<em>only</em> the Google Play app can handle billing requests from your app,
preventing other apps from intercepting those requests.
</p>
+<p>
+ The following code sample demonstrates how to set the intent's target package
+ to protect the security of transactions:
+</p>
+
<pre>@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -233,6 +238,13 @@
}
</pre>
+<p class="caution"><strong>Caution</strong>: To ensure that your app is secure, always use an
+explicit intent when starting a {@link android.app.Service} and do not declare intent filters for
+your services. Using an implicit intent to start a service is a security hazard because you cannot
+be certain of the service that will respond to the intent, and the user cannot see which service
+starts. Beginning with Android 5.0 (API level 21), the system throws an exception if you call
+{@link android.content.Context#bindService bindService()} with an implicit intent.</p>
+
<p>
You can now use the mService reference to communicate with the Google Play
service.
@@ -242,10 +254,14 @@
<strong>Important:</strong> Remember to unbind from the In-app Billing
service when you are done with your {@link android.app.Activity}. If you
don’t unbind, the open service connection could cause your device’s
- performance to degrade. This example shows how to perform the unbind
+ performance to degrade.
+</p>
+
+<p>
+ This example shows how to perform the unbind
operation on a service connection to In-app Billing called {@code
mServiceConn} by overriding the activity’s {@link
- android.app.Activity#onDestroy onDestroy} method.
+ android.app.Activity#onDestroy onDestroy} method:
</p>
<pre>
@@ -264,29 +280,29 @@
"{@docRoot}training/in-app-billing/preparing-iab-app.html">Selling In-app
Products</a> training class and associated sample.
</p>
-<h2 id="billing-requests">Making In-app Billing Requests</h2>
+<h2 id="billing-requests">Making In-app Billing requests</h2>
<p>
- Once your application is connected to Google Play, you can initiate purchase
+ After your application is connected to Google Play, you can initiate purchase
requests for in-app products. Google Play provides a checkout interface for
- users to enter their payment method, so your application does not need to
+ users to enter their payment method, so your application doesn't need to
handle payment transactions directly. When an item is purchased, Google Play
recognizes that the user has ownership of that item and prevents the user
from purchasing another item with the same product ID until it is consumed.
- You can control how the item is consumed in your application, and notify
+ You can control how the item is consumed in your application and notify
Google Play to make the item available for purchase again. You can also query
- Google Play to quickly retrieve the list of purchases that were made by the
- user. This is useful, for example, when you want to restore the user's
+ Google Play to quickly retrieve the list of purchases that the
+ user made. This is useful, for example, when you want to restore the user's
purchases when your user launches your app.
</p>
-<h3 id="QueryDetails">Querying for Items Available for Purchase</h3>
+<h3 id="QueryDetails">Querying for items available for purchase</h3>
<p>
In your application, you can query the item details from Google Play using
the In-app Billing Version 3 API. To pass a request to the In-app Billing
- service, first create a {@link android.os.Bundle} that contains a String
+ service, create a {@link android.os.Bundle} that contains a String
{@link java.util.ArrayList} of product IDs with key "ITEM_ID_LIST", where
- each string is a product ID for an purchasable item.
+ each string is a product ID for an purchasable item. Here is an example:
</p>
<pre>
@@ -299,9 +315,9 @@
<p>
To retrieve this information from Google Play, call the {@code getSkuDetails}
- method on the In-app Billing Version 3 API, and pass the method the In-app
+ method on the In-app Billing Version 3 API and pass the In-app
Billing API version (“3”), the package name of your calling app, the purchase
- type (“inapp”), and the {@link android.os.Bundle} that you created.
+ type (“inapp”), and the {@link android.os.Bundle} that you created, into the method:
</p>
<pre>
@@ -310,35 +326,35 @@
</pre>
<p>
- If the request is successful, the returned {@link android.os.Bundle}has a
+ If the request is successful, the returned {@link android.os.Bundle} has a
response code of {@code BILLING_RESPONSE_RESULT_OK} (0).
</p>
<p class="note">
- <strong>Warning:</strong> Do not call the {@code getSkuDetails} method on the
- main thread. Calling this method triggers a network request which could block
+ <strong>Warning:</strong> Don't call the {@code getSkuDetails} method on the
+ main thread. Calling this method triggers a network request that could block
your main thread. Instead, create a separate thread and call the {@code
- getSkuDetails} method from inside that thread.
+ getSkuDetails} method from inside of that thread.
</p>
<p>
- To see all the possible response codes from Google Play, see <a href=
+ To view all of the possible response codes from Google Play, see <a href=
"{@docRoot}google/play/billing/billing_reference.html#billing-codes">In-app
Billing Reference</a>.
</p>
<p>
The query results are stored in a String ArrayList with key {@code
- DETAILS_LIST}. The purchase information is stored in the String in JSON
- format. To see the types of product detail information that are returned, see
+ DETAILS_LIST}. The purchase information is stored within the String in JSON
+ format. To view the types of product detail information that are returned, see
<a href=
"{@docRoot}google/play/billing/billing_reference.html#getSkuDetails">In-app
Billing Reference</a>.
</p>
<p>
- In this example, you are retrieving the prices for your in-app items from the
- skuDetails {@link android.os.Bundle} returned from the previous code snippet.
+ In this example shows how to retrieve the prices for your in-app items from the
+ skuDetails {@link android.os.Bundle} that is returned from the previous code snippet:
</p>
<pre>
@@ -357,15 +373,15 @@
}
</pre>
-<h3 id="Purchase">Purchasing an Item</h3>
+<h3 id="Purchase">Purchasing an item</h3>
<p>
To start a purchase request from your app, call the {@code getBuyIntent}
- method on the In-app Billing service. Pass in to the method the In-app
+ method on the In-app Billing service. Pass the In-app
Billing API version (“3”), the package name of your calling app, the product
ID for the item to purchase, the purchase type (“inapp” or "subs"), and a
- {@code developerPayload} String. The {@code developerPayload} String is used
+ {@code developerPayload} String into the method. The {@code developerPayload} String is used
to specify any additional arguments that you want Google Play to send back
- along with the purchase information.
+ along with the purchase information. Here is an example:
</p>
<pre>
@@ -377,10 +393,13 @@
If the request is successful, the returned {@link android.os.Bundle} has a
response code of {@code BILLING_RESPONSE_RESULT_OK} (0) and a {@link
android.app.PendingIntent} that you can use to start the purchase flow. To
- see all the possible response codes from Google Play, see <a href=
+ view all of the possible response codes from Google Play, see <a href=
"{@docRoot}google/play/billing/billing_reference.html#billing-codes">In-app
- Billing Reference</a>. Next, extract a {@link android.app.PendingIntent} from
- the response {@link android.os.Bundle} with key {@code BUY_INTENT}.
+ Billing Reference</a>.
+
+<p>
+ The next step is to extract a {@link android.app.PendingIntent} from
+ the response {@link android.os.Bundle} with key {@code BUY_INTENT}, as shown here:
</p>
<pre>
@@ -390,8 +409,8 @@
<p>
To complete the purchase transaction, call the {@link
android.app.Activity#startIntentSenderForResult startIntentSenderForResult}
- method and use the {@link android.app.PendingIntent} that you created. In
- this example, you are using an arbitrary value of 1001 for the request code.
+ method and use the {@link android.app.PendingIntent} that you created. This
+ example uses an arbitrary value of 1001 for the request code:
</p>
<pre>
@@ -404,9 +423,9 @@
Google Play sends a response to your {@link android.app.PendingIntent} to the
{@link android.app.Activity#onActivityResult onActivityResult} method of your
application. The {@link android.app.Activity#onActivityResult
- onActivityResult} method will have a result code of {@code
- Activity.RESULT_OK} (1) or {@code Activity.RESULT_CANCELED} (0). To see the
- types of order information that is returned in the response {@link
+ onActivityResult} method has a result code of {@code
+ Activity.RESULT_OK} (1) or {@code Activity.RESULT_CANCELED} (0). To view the
+ types of order information that are returned in the response {@link
android.content.Intent}, see <a href=
"{@docRoot}google/play/billing/billing_reference.html#getBuyIntent">In-app
Billing Reference</a>.
@@ -415,7 +434,7 @@
<p>
The purchase data for the order is a String in JSON format that is mapped to
the {@code INAPP_PURCHASE_DATA} key in the response {@link
- android.content.Intent}, for example:
+ android.content.Intent}. Here is an example:
</p>
<pre>
@@ -436,13 +455,13 @@
long. Pass this entire token to other methods, such as when you consume the
purchase, as described in <a href=
"{@docRoot}training/in-app-billing/purchase-iab-products.html#Consume">Consume
- a Purchase</a>. Do not abbreviate or truncate this token; you must save and
+ a Purchase</a>. Don't abbreviate or truncate this token; you must save and
return the entire token.
</p>
<p>
- Continuing from the previous example, you get the response code, purchase
- data, and signature from the response {@link android.content.Intent}.
+ Continuing from the previous example, you receive the response code, purchase
+ data, and signature from the response {@link android.content.Intent}:
</p>
<pre>
@@ -472,23 +491,23 @@
<p class="note">
<strong>Security Recommendation:</strong> When you send a purchase request,
create a String token that uniquely identifies this purchase request and
- include this token in the {@code developerPayload}.You can use a randomly
- generated string as the token. When you receive the purchase response from
- Google Play, make sure to check the returned data signature, the {@code
+ include this token in the {@code developerPayload}. You can use a randomly-generated
+ string as the token. When you receive the purchase response from
+ Google Play, ensure that you check the returned data signature, the {@code
orderId}, and the {@code developerPayload} String. For added security, you
- should perform the checking on your own secure server. Make sure to verify
+ should perform the checking on your own secure server. Verify
that the {@code orderId} is a unique value that you have not previously
- processed, and the {@code developerPayload} String matches the token that you
+ processed and that the {@code developerPayload} String matches the token that you
sent previously with the purchase request.
</p>
-<h3 id="QueryPurchases">Querying for Purchased Items</h3>
+<h3 id="QueryPurchases">Querying for purchased items</h3>
<p>
- To retrieve information about purchases made by a user from your app, call
+ To retrieve information about purchases that are made by a user from your app, call
the {@code getPurchases} method on the In-app Billing Version 3 service. Pass
- in to the method the In-app Billing API version (“3”), the package name of
- your calling app, and the purchase type (“inapp” or "subs").
+ the In-app Billing API version (“3”), the package name of
+ your calling app, and the purchase type (“inapp” or "subs") into the method. Here is an example:
</p>
<pre>
@@ -507,18 +526,18 @@
To improve performance, the In-app Billing service returns only up to 700
products that are owned by the user when {@code getPurchase} is first called.
If the user owns a large number of products, Google Play includes a String
- token mapped to the key {@code INAPP_CONTINUATION_TOKEN} in the response
+ token that is mapped to the key {@code INAPP_CONTINUATION_TOKEN} in the response
{@link android.os.Bundle} to indicate that more products can be retrieved.
- Your application can then make a subsequent {@code getPurchases} call, and
+ Your application can then make a subsequent {@code getPurchases} call and
pass in this token as an argument. Google Play continues to return a
continuation token in the response {@link android.os.Bundle} until all
- products that are owned by the user has been sent to your app.
+ of the products that are owned by the user are sent to your app.
</p>
-<p>For more information about the data returned by {@code getPurchases}, see
+<p>For more information about the data that is returned by {@code getPurchases}, see
<a href="{@docRoot}google/play/billing/billing_reference.html#getPurchases">
In-app Billing Reference</a>. The following example shows how you can
- retrieve this data from the response.
+ retrieve this data from the response:
</p>
<pre>
@@ -548,26 +567,26 @@
</pre>
-<h3 id="Consume">Consuming a Purchase</h3>
+<h3 id="Consume">Consuming a purchase</h3>
<p>
You can use the In-app Billing Version 3 API to track the ownership of
purchased in-app products in Google Play. Once an in-app product is
- purchased, it is considered to be "owned" and cannot be purchased from Google
+ purchased, it is considered to be <em>owned</em> and cannot be purchased from Google
Play. You must send a consumption request for the in-app product before
Google Play makes it available for purchase again.
</p>
-<p class="caution">
+<p class="note">
<strong>Important</strong>: Managed in-app products are consumable, but
subscriptions are not.
</p>
<p>
- How you use the consumption mechanism in your app is up to you. Typically,
- you would implement consumption for in-app products with temporary benefits
+ The way that you use the consumption mechanism in your app is up to you. Typically,
+ you implement consumption for in-app products with temporary benefits
that users may want to purchase multiple times (for example, in-game currency
- or equipment). You would typically not want to implement consumption for
+ or equipment). You typically don't want to implement consumption for
in-app products that are purchased once and provide a permanent effect (for
example, a premium upgrade).
</p>
@@ -576,21 +595,21 @@
To record a purchase consumption, send the {@code consumePurchase} method to
the In-app Billing service and pass in the {@code purchaseToken} String value
that identifies the purchase to be removed. The {@code purchaseToken} is part
- of the data returned in the {@code INAPP_PURCHASE_DATA} String by the Google
- Play service following a successful purchase request. In this example, you
- are recording the consumption of a product that is identified with the {@code
- purchaseToken} in the {@code token} variable.
+ of the data that is returned in the {@code INAPP_PURCHASE_DATA} String by the Google
+ Play service following a successful purchase request. This example
+ records the consumption of a product that is identified with the {@code
+ purchaseToken} in the {@code token} variable:
</p>
<pre>
int response = mService.consumePurchase(3, getPackageName(), token);
</pre>
-<p class="note">
- <strong>Warning:</strong> Do not call the {@code consumePurchase} method on
- the main thread. Calling this method triggers a network request which could
+<p class="caution">
+ <strong>Warning:</strong> Don't call the {@code consumePurchase} method on
+ the main thread. Calling this method triggers a network request that could
block your main thread. Instead, create a separate thread and call the {@code
- consumePurchase} method from inside that thread.
+ consumePurchase} method from inside of that thread.
</p>
<p>
@@ -600,20 +619,20 @@
purchased.
</p>
-<p class="note">
- <strong>Security Recommendation:</strong> You must send a consumption request
+<p class="caution">
+ <strong>Security Recommendation:</strong> Send a consumption request
before provisioning the benefit of the consumable in-app purchase to the
- user. Make sure that you have received a successful consumption response from
+ user. Ensure that you receive a successful consumption response from
Google Play before you provision the item.
</p>
-<h3 id="Subs">Implementing Subscriptions</h3>
+<h3 id="Subs">Implementing subscriptions</h3>
<p>Launching a purchase flow for a subscription is similar to launching the
purchase flow for a product, with the exception that the product type must be set
to "subs". The purchase result is delivered to your Activity's
{@link android.app.Activity#onActivityResult onActivityResult} method, exactly
-as in the case of in-app products.</p>
+as in the case of in-app products. Here is an example:</p>
<pre>
Bundle bundle = mService.getBuyIntent(3, "com.example.myapp",
@@ -629,18 +648,18 @@
</pre>
<p>To query for active subscriptions, use the {@code getPurchases} method, again
-with the product type parameter set to "subs".</p>
+with the product type parameter set to "subs":</p>
<pre>
Bundle activeSubs = mService.getPurchases(3, "com.example.myapp",
"subs", continueToken);
</pre>
-<p>The call returns a {@code Bundle} with all the active subscriptions owned by
-the user. Once a subscription expires without renewal, it will no longer appear
+<p>The call returns a {@code Bundle} with all of the active subscriptions that are owned by
+the user. When a subscription expires without renewal, it no longer appears
in the returned {@code Bundle}.</p>
-<h2 id="billing-security">Securing Your Application</h2>
+<h2 id="billing-security">Securing your application</h2>
<p>To help ensure the integrity of the transaction information that is sent to
your application, Google Play signs the JSON string that contains the response
@@ -648,21 +667,21 @@
with your application in the Developer Console to create this signature. The
Developer Console generates an RSA key pair for each application.<p>
-<p class="note"><strong>Note:</strong>To find the public key portion of this key
-pair, open your application's details in the Developer Console, then click on
-<strong>Services & APIs</strong>, and look at the field titled
+<p class="note"><strong>Note:</strong> To find the public key portion of this key
+pair, open your application's details in the Developer Console, click
+<strong>Services & APIs</strong>, and review the field titled
<strong>Your License Key for This Application</strong>.</p>
-<p>The Base64-encoded RSA public key generated by Google Play is in binary
+<p>The Base64-encoded RSA public key that is generated by Google Play is in binary
encoded, X.509 subjectPublicKeyInfo DER SEQUENCE format. It is the same public
key that is used with Google Play licensing.</p>
-<p>When your application receives this signed response you can
+<p>When your application receives this signed response, you can
use the public key portion of your RSA key pair to verify the signature.
-By performing signature verification you can detect responses that have
+By performing signature verification, you can detect any responses that have
been tampered with or that have been spoofed. You can perform this signature
verification step in your application; however, if your application connects
-to a secure remote server then we recommend that you perform the signature
+to a secure remote server, Google recommends that you perform the signature
verification on that server.</p>
<p>For more information about best practices for security and design, see <a
diff --git a/docs/html/guide/components/bound-services.jd b/docs/html/guide/components/bound-services.jd
index f71ba87..2ee2061 100644
--- a/docs/html/guide/components/bound-services.jd
+++ b/docs/html/guide/components/bound-services.jd
@@ -8,19 +8,19 @@
<ol id="qv">
<h2>In this document</h2>
<ol>
- <li><a href="#Basics">The Basics</a></li>
- <li><a href="#Creating">Creating a Bound Service</a>
+ <li><a href="#Basics">The basics</a></li>
+ <li><a href="#Creating">Creating a bound service</a>
<ol>
<li><a href="#Binder">Extending the Binder class</a></li>
<li><a href="#Messenger">Using a Messenger</a></li>
</ol>
</li>
- <li><a href="#Binding">Binding to a Service</a>
+ <li><a href="#Binding">Binding to a service</a>
<ol>
<li><a href="#Additional_Notes">Additional notes</a></li>
</ol>
</li>
- <li><a href="#Lifecycle">Managing the Lifecycle of a Bound Service</a></li>
+ <li><a href="#Lifecycle">Managing the lifecycle of a bound service</a></li>
</ol>
<h2>Key classes</h2>
@@ -32,9 +32,13 @@
<h2>Samples</h2>
<ol>
- <li><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/RemoteService.html">{@code
+ <li><a
+ href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/RemoteService.html">
+ {@code
RemoteService}</a></li>
- <li><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LocalService.html">{@code
+ <li><a
+ href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LocalService.html">
+ {@code
LocalService}</a></li>
</ol>
@@ -45,19 +49,23 @@
</div>
-<p>A bound service is the server in a client-server interface. A bound service allows components
-(such as activities) to bind to the service, send requests, receive responses, and even perform
+<p>A bound service is the server in a client-server interface. It allows components
+(such as activities) to bind to the service, send requests, receive responses, and perform
interprocess communication (IPC). A bound service typically lives only while it serves another
application component and does not run in the background indefinitely.</p>
-<p>This document shows you how to create a bound service, including how to bind
-to the service from other application components. However, you should also refer to the <a
-href="{@docRoot}guide/components/services.html">Services</a> document for additional
-information about services in general, such as how to deliver notifications from a service, set
-the service to run in the foreground, and more.</p>
+<p class="note"><strong>Note:</strong> If your app targets Android 5.0 (API level 21) or later,
+it's recommended that you use the {@link android.app.job.JobScheduler} to execute background
+ services. For more information about {@link android.app.job.JobScheduler}, see its
+ {@link android.app.job.JobScheduler API-reference documentation}.</p>
+<p>This document describes how to create a bound service, including how to bind
+to the service from other application components. For additional information about services in
+ general, such as how to deliver notifications from a service and set the service to run
+ in the foreground, refer to the <a href="{@docRoot}guide/components/services.html">
+ Services</a> document.</p>
-<h2 id="Basics">The Basics</h2>
+<h2 id="Basics">The basics</h2>
<p>A bound service is an implementation of the {@link android.app.Service} class that allows
other applications to bind to it and interact with it. To provide binding for a
@@ -67,57 +75,61 @@
<div class="sidebox-wrapper">
<div class="sidebox">
- <h3>Binding to a Started Service</h3>
+ <h3>Binding to a started service</h3>
<p>As discussed in the <a href="{@docRoot}guide/components/services.html">Services</a>
-document, you can create a service that is both started and bound. That is, the service can be
-started by calling {@link android.content.Context#startService startService()}, which allows the
-service to run indefinitely, and also allow a client to bind to the service by calling {@link
+document, you can create a service that is both started and bound. That is, you can start a
+ service by calling {@link android.content.Context#startService startService()}, which allows the
+service to run indefinitely, and you can also allow a client to bind to the service by
+ calling {@link
android.content.Context#bindService bindService()}.
<p>If you do allow your service to be started and bound, then when the service has been
-started, the system does <em>not</em> destroy the service when all clients unbind. Instead, you must
-explicitly stop the service, by calling {@link android.app.Service#stopSelf stopSelf()} or {@link
+started, the system does <em>not</em> destroy the service when all clients unbind.
+ Instead, you must
+explicitly stop the service by calling {@link android.app.Service#stopSelf stopSelf()} or {@link
android.content.Context#stopService stopService()}.</p>
-<p>Although you should usually implement either {@link android.app.Service#onBind onBind()}
-<em>or</em> {@link android.app.Service#onStartCommand onStartCommand()}, it's sometimes necessary to
+<p>Although you usually implement either {@link android.app.Service#onBind onBind()}
+<em>or</em> {@link android.app.Service#onStartCommand onStartCommand()}, it's sometimes
+ necessary to
implement both. For example, a music player might find it useful to allow its service to run
indefinitely and also provide binding. This way, an activity can start the service to play some
music and the music continues to play even if the user leaves the application. Then, when the user
-returns to the application, the activity can bind to the service to regain control of playback.</p>
+returns to the application, the activity can bind to the service to regain control of
+ playback.</p>
-<p>Be sure to read the section about <a href="#Lifecycle">Managing the Lifecycle of a Bound
-Service</a>, for more information about the service lifecycle when adding binding to a
-started service.</p>
+<p>For more information about the service lifecycle when adding binding to a started service,
+ see <a href="#Lifecycle">Managing the lifecycle of a bound Service</a>.</p>
</div>
</div>
-<p>A client can bind to the service by calling {@link android.content.Context#bindService
+<p>A client can bind to a service by calling {@link android.content.Context#bindService
bindService()}. When it does, it must provide an implementation of {@link
android.content.ServiceConnection}, which monitors the connection with the service. The {@link
-android.content.Context#bindService bindService()} method returns immediately without a value, but
+android.content.Context#bindService bindService()} method returns immediately without a
+ value, but
when the Android system creates the connection between the
client and service, it calls {@link
android.content.ServiceConnection#onServiceConnected onServiceConnected()} on the {@link
android.content.ServiceConnection}, to deliver the {@link android.os.IBinder} that
the client can use to communicate with the service.</p>
-<p>Multiple clients can connect to the service at once. However, the system calls your service's
-{@link android.app.Service#onBind onBind()} method to retrieve the {@link android.os.IBinder} only
+<p>Multiple clients can connect to a service simultaneously. However, the system calls your service's
+{@link android.app.Service#onBind onBind()} method to retrieve the
+ {@link android.os.IBinder} only
when the first client binds. The system then delivers the same {@link android.os.IBinder} to any
-additional clients that bind, without calling {@link android.app.Service#onBind onBind()} again.</p>
+additional clients that bind, without calling {@link android.app.Service#onBind onBind()}
+ again.</p>
-<p>When the last client unbinds from the service, the system destroys the service (unless the
-service was also started by {@link android.content.Context#startService startService()}).</p>
+<p>When the last client unbinds from the service, the system destroys the service, unless the
+service was also started by {@link android.content.Context#startService startService()}.</p>
-<p>When you implement your bound service, the most important part is defining the interface
-that your {@link android.app.Service#onBind onBind()} callback method returns. There are a few
-different ways you can define your service's {@link android.os.IBinder} interface and the following
-section discusses each technique.</p>
+<p>The most important part of your bound service implementation is defining the interface
+that your {@link android.app.Service#onBind onBind()} callback method returns. The following
+section discusses several different ways that you can define your service's
+ {@link android.os.IBinder} interface.</p>
-
-
-<h2 id="Creating">Creating a Bound Service</h2>
+<h2 id="Creating">Creating a bound service</h2>
<p>When creating a service that provides binding, you must provide an {@link android.os.IBinder}
that provides the programming interface that clients can use to interact with the service. There
@@ -125,12 +137,14 @@
<dl>
<dt><a href="#Binder">Extending the Binder class</a></dt>
- <dd>If your service is private to your own application and runs in the same process as the client
-(which is common), you should create your interface by extending the {@link android.os.Binder} class
+ <dd>If your service is private to your own application and runs in the same process
+ as the client
+(which is common), you should create your interface by extending the {@link android.os.Binder}
+ class
and returning an instance of it from
{@link android.app.Service#onBind onBind()}. The client receives the {@link android.os.Binder} and
can use it to directly access public methods available in either the {@link android.os.Binder}
-implementation or even the {@link android.app.Service}.
+implementation or the {@link android.app.Service}.
<p>This is the preferred technique when your service is merely a background worker for your own
application. The only reason you would not create your interface this way is because
your service is used by other applications or across separate processes.</dd>
@@ -143,20 +157,20 @@
is the basis for a {@link android.os.Messenger} that can then share an {@link android.os.IBinder}
with the client, allowing the client to send commands to the service using {@link
android.os.Message} objects. Additionally, the client can define a {@link android.os.Messenger} of
-its own so the service can send messages back.
+its own, so the service can send messages back.
<p>This is the simplest way to perform interprocess communication (IPC), because the {@link
android.os.Messenger} queues all requests into a single thread so that you don't have to design
your service to be thread-safe.</p>
</dd>
- <dt>Using AIDL</dt>
- <dd>AIDL (Android Interface Definition Language) performs all the work to decompose objects into
-primitives that the operating system can understand and marshall them across processes to perform
+ <dt><a href="{@docRoot}guide/components/aidl.html">Using AIDL</a></dt>
+ <dd>Android Interface Definition Language (AIDL) decomposes objects into
+primitives that the operating system can understand and marshals them across processes to perform
IPC. The previous technique, using a {@link android.os.Messenger}, is actually based on AIDL as
its underlying structure. As mentioned above, the {@link android.os.Messenger} creates a queue of
all the client requests in a single thread, so the service receives requests one at a time. If,
however, you want your service to handle multiple requests simultaneously, then you can use AIDL
-directly. In this case, your service must be capable of multi-threading and be built thread-safe.
+directly. In this case, your service must be thread-safe and capable of multi-threading.
<p>To use AIDL directly, you must
create an {@code .aidl} file that defines the programming interface. The Android SDK tools use
this file to generate an abstract class that implements the interface and handles IPC, which you
@@ -164,19 +178,18 @@
</dd>
</dl>
- <p class="note"><strong>Note:</strong> Most applications <strong>should not</strong> use AIDL to
+ <p class="note"><strong>Note:</strong> Most applications <em>shouldn't</em> use AIDL to
create a bound service, because it may require multithreading capabilities and
-can result in a more complicated implementation. As such, AIDL is not suitable for most applications
+can result in a more complicated implementation. As such, AIDL is not suitable for
+ most applications
and this document does not discuss how to use it for your service. If you're certain that you need
to use AIDL directly, see the <a href="{@docRoot}guide/components/aidl.html">AIDL</a>
document.</p>
-
-
-
<h3 id="Binder">Extending the Binder class</h3>
-<p>If your service is used only by the local application and does not need to work across processes,
+<p>If your service is used only by the local application and does not need to
+ work across processes,
then you can implement your own {@link android.os.Binder} class that provides your client direct
access to public methods in the service.</p>
@@ -187,13 +200,14 @@
<p>Here's how to set it up:</p>
<ol>
- <li>In your service, create an instance of {@link android.os.Binder} that either:
+ <li>In your service, create an instance of {@link android.os.Binder} that does
+ one of the following:
<ul>
- <li>contains public methods that the client can call</li>
- <li>returns the current {@link android.app.Service} instance, which has public methods the
-client can call</li>
- <li>or, returns an instance of another class hosted by the service with public methods the
-client can call</li>
+ <li>Contains public methods that the client can call.</li>
+ <li>Returns the current {@link android.app.Service} instance, which has public methods the
+client can call.</li>
+ <li>Returns an instance of another class hosted by the service with public methods the
+client can call.</li>
</ul>
<li>Return this instance of {@link android.os.Binder} from the {@link
android.app.Service#onBind onBind()} callback method.</li>
@@ -202,12 +216,13 @@
make calls to the bound service using the methods provided.</li>
</ol>
-<p class="note"><strong>Note:</strong> The reason the service and client must be in the same
-application is so the client can cast the returned object and properly call its APIs. The service
+<p class="note"><strong>Note:</strong> The service and client must be in the same
+application so that the client can cast the returned object and properly call its APIs.
+ The service
and client must also be in the same process, because this technique does not perform any
-marshalling across processes.</p>
+marshaling across processes.</p>
-<p>For example, here's a service that provides clients access to methods in the service through
+<p>For example, here's a service that provides clients with access to methods in the service through
a {@link android.os.Binder} implementation:</p>
<pre>
@@ -316,32 +331,30 @@
<p class="note"><strong>Note:</strong> In the example above, the
{@link android.app.Activity#onStop onStop()} method unbinds the client from the service. Clients
should unbind from services at appropriate times, as discussed in
-<a href="#Additional_Notes">Additional Notes</a>.
+<a href="#Additional_Notes">Additional notes</a>.
</p>
<p>For more sample code, see the <a
-href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LocalService.html">{@code
+href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LocalService.html">
+{@code
LocalService.java}</a> class and the <a
-href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LocalServiceActivities.html">{@code
+href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LocalServiceActivities.html">
+{@code
LocalServiceActivities.java}</a> class in <a
href="{@docRoot}resources/samples/ApiDemos/index.html">ApiDemos</a>.</p>
-
-
-
-
<h3 id="Messenger">Using a Messenger</h3>
<div class="sidebox-wrapper">
<div class="sidebox">
<h4>Compared to AIDL</h4>
<p>When you need to perform IPC, using a {@link android.os.Messenger} for your interface is
-simpler than implementing it with AIDL, because {@link android.os.Messenger} queues
-all calls to the service, whereas, a pure AIDL interface sends simultaneous requests to the
+simpler than using AIDL, because {@link android.os.Messenger} queues
+all calls to the service. A pure AIDL interface sends simultaneous requests to the
service, which must then handle multi-threading.</p>
<p>For most applications, the service doesn't need to perform multi-threading, so using a {@link
android.os.Messenger} allows the service to handle one call at a time. If it's important
-that your service be multi-threaded, then you should use <a
+that your service be multi-threaded, use <a
href="{@docRoot}guide/components/aidl.html">AIDL</a> to define your interface.</p>
</div>
</div>
@@ -352,10 +365,11 @@
<p>Here's a summary of how to use a {@link android.os.Messenger}:</p>
-<ul>
+<ol>
<li>The service implements a {@link android.os.Handler} that receives a callback for each
call from a client.</li>
- <li>The {@link android.os.Handler} is used to create a {@link android.os.Messenger} object
+ <li>The service uses the {@link android.os.Handler} to create a {@link android.os.Messenger}
+ object
(which is a reference to the {@link android.os.Handler}).</li>
<li>The {@link android.os.Messenger} creates an {@link android.os.IBinder} that the service
returns to clients from {@link android.app.Service#onBind onBind()}.</li>
@@ -365,11 +379,12 @@
<li>The service receives each {@link android.os.Message} in its {@link
android.os.Handler}—specifically, in the {@link android.os.Handler#handleMessage
handleMessage()} method.</li>
-</ul>
+</ol>
-<p>In this way, there are no "methods" for the client to call on the service. Instead, the
-client delivers "messages" ({@link android.os.Message} objects) that the service receives in
+<p>In this way, there are no <em>methods</em> for the client to call on the service. Instead, the
+client delivers <em>messages</em> ({@link android.os.Message} objects) that the service
+ receives in
its {@link android.os.Handler}.</p>
<p>Here's a simple example service that uses a {@link android.os.Messenger} interface:</p>
@@ -488,41 +503,42 @@
}
</pre>
-<p>Notice that this example does not show how the service can respond to the client. If you want the
-service to respond, then you need to also create a {@link android.os.Messenger} in the client. Then
-when the client receives the {@link android.content.ServiceConnection#onServiceConnected
+<p>Notice that this example does not show how the service can respond to the client.
+ If you want the
+service to respond, you need to also create a {@link android.os.Messenger} in the client.
+When the client receives the {@link android.content.ServiceConnection#onServiceConnected
onServiceConnected()} callback, it sends a {@link android.os.Message} to the service that includes
the client's {@link android.os.Messenger} in the {@link android.os.Message#replyTo} parameter
of the {@link android.os.Messenger#send send()} method.</p>
<p>You can see an example of how to provide two-way messaging in the <a
-href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/MessengerService.html">{@code
+href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/MessengerService.html">
+{@code
MessengerService.java}</a> (service) and <a
-href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/MessengerServiceActivities.html">{@code
+href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/MessengerServiceActivities.html">
+{@code
MessengerServiceActivities.java}</a> (client) samples.</p>
-
-
-
-
-<h2 id="Binding">Binding to a Service</h2>
+<h2 id="Binding">Binding to a service</h2>
<p>Application components (clients) can bind to a service by calling
{@link android.content.Context#bindService bindService()}. The Android
system then calls the service's {@link android.app.Service#onBind
-onBind()} method, which returns an {@link android.os.IBinder} for interacting with the service.</p>
+onBind()} method, which returns an {@link android.os.IBinder} for interacting with
+ the service.</p>
-<p>The binding is asynchronous. {@link android.content.Context#bindService
-bindService()} returns immediately and does <em>not</em> return the {@link android.os.IBinder} to
-the client. To receive the {@link android.os.IBinder}, the client must create an instance of {@link
+<p>The binding is asynchronous, and {@link android.content.Context#bindService
+bindService()} returns immediately without <em>not</em> returning the {@link android.os.IBinder} to
+the client. To receive the {@link android.os.IBinder}, the client must create an
+ instance of {@link
android.content.ServiceConnection} and pass it to {@link android.content.Context#bindService
bindService()}. The {@link android.content.ServiceConnection} includes a callback method that the
system calls to deliver the {@link android.os.IBinder}.</p>
<p class="note"><strong>Note:</strong> Only activities, services, and content providers can bind
-to a service—you <strong>cannot</strong> bind to a service from a broadcast receiver.</p>
+to a service—you <strong>can't</strong> bind to a service from a broadcast receiver.</p>
-<p>So, to bind to a service from your client, you must: </p>
+<p>To bind to a service from your client, follow these steps: </p>
<ol>
<li>Implement {@link android.content.ServiceConnection}.
<p>Your implementation must override two callback methods:</p>
@@ -533,7 +549,8 @@
<dt>{@link android.content.ServiceConnection#onServiceDisconnected
onServiceDisconnected()}</dt>
<dd>The Android system calls this when the connection to the service is unexpectedly
-lost, such as when the service has crashed or has been killed. This is <em>not</em> called when the
+lost, such as when the service has crashed or has been killed. This is <em>not</em>
+ called when the
client unbinds.</dd>
</dl>
</li>
@@ -548,12 +565,12 @@
<p>If your client is still bound to a service when your app destroys the client, destruction
causes the client to unbind. It is better practice to unbind the client as soon as it is done
interacting with the service. Doing so allows the idle service to shut down. For more information
-about appropriate times to bind and unbind, see <a href="#Additional_Notes">Additional Notes</a>.
+about appropriate times to bind and unbind, see <a href="#Additional_Notes">Additional notes</a>.
</p>
</li>
</ol>
-<p>For example, the following snippet connects the client to the service created above by
+<p>The following example connects the client to the service created above by
<a href="#Binder">extending the Binder class</a>, so all it must do is cast the returned
{@link android.os.IBinder} to the {@code LocalService} class and request the {@code
LocalService} instance:</p>
@@ -579,8 +596,9 @@
};
</pre>
-<p>With this {@link android.content.ServiceConnection}, the client can bind to a service by passing
-it to {@link android.content.Context#bindService bindService()}. For example:</p>
+<p>With this {@link android.content.ServiceConnection}, the client can bind to a service
+ by passing
+it to {@link android.content.Context#bindService bindService()}, as shown in the following example:</p>
<pre>
Intent intent = new Intent(this, LocalService.class);
@@ -589,11 +607,21 @@
<ul>
<li>The first parameter of {@link android.content.Context#bindService bindService()} is an
-{@link android.content.Intent} that explicitly names the service to bind (thought the intent
-could be implicit).</li>
+{@link android.content.Intent} that explicitly names the service to bind.
+<p class="caution"><strong>Caution:</strong> If you use an intent to bind to a
+ {@link android.app.Service}, ensure that your app is secure by using an <a href="{@docRoot}guide/components/intents-filters.html#Types">explicit</a>
+intent. Using an implicit intent to start a service is a
+security hazard because you can't be certain what service will respond to the intent,
+and the user can't see which service starts. Beginning with Android 5.0 (API level 21),
+ the system
+throws an exception if you call {@link android.content.Context#bindService bindService()}
+with an implicit intent.</p>
+</li>
+
<li>The second parameter is the {@link android.content.ServiceConnection} object.</li>
<li>The third parameter is a flag indicating options for the binding. It should usually be {@link
-android.content.Context#BIND_AUTO_CREATE} in order to create the service if its not already alive.
+android.content.Context#BIND_AUTO_CREATE} in order to create the service if it's not already
+ alive.
Other possible values are {@link android.content.Context#BIND_DEBUG_UNBIND}
and {@link android.content.Context#BIND_NOT_FOREGROUND}, or {@code 0} for none.</li>
</ul>
@@ -606,10 +634,11 @@
<li>You should always trap {@link android.os.DeadObjectException} exceptions, which are thrown
when the connection has broken. This is the only exception thrown by remote methods.</li>
<li>Objects are reference counted across processes. </li>
- <li>You should usually pair the binding and unbinding during
-matching bring-up and tear-down moments of the client's lifecycle. For example:
+ <li>You usually pair the binding and unbinding during
+matching bring-up and tear-down moments of the client's lifecycle, as described in the
+ following examples:
<ul>
- <li>If you only need to interact with the service while your activity is visible, you
+ <li>If you need to interact with the service only while your activity is visible, you
should bind during {@link android.app.Activity#onStart onStart()} and unbind during {@link
android.app.Activity#onStop onStop()}.</li>
<li>If you want your activity to receive responses even while it is stopped in the
@@ -619,33 +648,34 @@
the service is in another process, then you increase the weight of the process and it becomes
more likely that the system will kill it.</li>
</ul>
- <p class="note"><strong>Note:</strong> You should usually <strong>not</strong> bind and unbind
+ <p class="note"><strong>Note:</strong> You <em>don't</em> usually bind and unbind
during your activity's {@link android.app.Activity#onResume onResume()} and {@link
-android.app.Activity#onPause onPause()}, because these callbacks occur at every lifecycle transition
+android.app.Activity#onPause onPause()}, because these callbacks occur at every
+ lifecycle transition
and you should keep the processing that occurs at these transitions to a minimum. Also, if
-multiple activities in your application bind to the same service and there is a transition between
-two of those activities, the service may be destroyed and recreated as the current activity unbinds
-(during pause) before the next one binds (during resume). (This activity transition for how
+multiple activities in your application bind to the same service and there is a
+ transition between
+two of those activities, the service may be destroyed and recreated as the current
+ activity unbinds
+(during pause) before the next one binds (during resume). This activity transition for how
activities coordinate their lifecycles is described in the <a
href="{@docRoot}guide/components/activities.html#CoordinatingActivities">Activities</a>
-document.)</p>
+document.</p>
</ul>
<p>For more sample code, showing how to bind to a service, see the <a
-href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/RemoteService.html">{@code
+href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/RemoteService.html">
+{@code
RemoteService.java}</a> class in <a
href="{@docRoot}resources/samples/ApiDemos/index.html">ApiDemos</a>.</p>
-
-
-
-
-<h2 id="Lifecycle">Managing the Lifecycle of a Bound Service</h2>
+<h2 id="Lifecycle">Managing the lifecycle of a bound service</h2>
<p>When a service is unbound from all clients, the Android system destroys it (unless it was also
started with {@link android.app.Service#onStartCommand onStartCommand()}). As such, you don't have
to manage the lifecycle of your service if it's purely a bound
-service—the Android system manages it for you based on whether it is bound to any clients.</p>
+service—the Android system manages it for you based on whether it is bound to
+ any clients.</p>
<p>However, if you choose to implement the {@link android.app.Service#onStartCommand
onStartCommand()} callback method, then you must explicitly stop the service, because the
@@ -660,17 +690,11 @@
onRebind()} the next time a client binds to the service. {@link android.app.Service#onRebind
onRebind()} returns void, but the client still receives the {@link android.os.IBinder} in its
{@link android.content.ServiceConnection#onServiceConnected onServiceConnected()} callback.
-Below, figure 1 illustrates the logic for this kind of lifecycle.</p>
-
+The following figure illustrates the logic for this kind of lifecycle.</p>
<img src="{@docRoot}images/fundamentals/service_binding_tree_lifecycle.png" alt="" />
<p class="img-caption"><strong>Figure 1.</strong> The lifecycle for a service that is started
and also allows binding.</p>
-
<p>For more information about the lifecycle of a started service, see the <a
href="{@docRoot}guide/components/services.html#Lifecycle">Services</a> document.</p>
-
-
-
-
diff --git a/docs/html/guide/components/fundamentals.jd b/docs/html/guide/components/fundamentals.jd
index ed3ba7d..eaa82c8 100644
--- a/docs/html/guide/components/fundamentals.jd
+++ b/docs/html/guide/components/fundamentals.jd
@@ -6,28 +6,29 @@
<h2>In this document</h2>
<ol>
-<li><a href="#Components">App Components</a>
+<li><a href="#Components">App components</a>
<ol>
<li><a href="#ActivatingComponents">Activating components</a></li>
</ol>
</li>
-<li><a href="#Manifest">The Manifest File</a>
+<li><a href="#Manifest">The manifest file</a>
<ol>
<li><a href="#DeclaringComponents">Declaring components</a></li>
<li><a href="#DeclaringRequirements">Declaring app requirements</a></li>
</ol>
</li>
-<li><a href="#Resources">App Resources</a></li>
+<li><a href="#Resources">App resources</a></li>
</ol>
</div>
</div>
<p>Android apps are written in the Java programming language. The Android SDK tools compile
-your code—along with any data and resource files—into an APK: an <i>Android package</i>,
+your code along with any data and resource files into an APK, an <i>Android package</i>,
which is an archive file with an {@code .apk} suffix. One APK file contains all the contents
of an Android app and is the file that Android-powered devices use to install the app.</p>
-<p>Once installed on a device, each Android app lives in its own security sandbox: </p>
+<p>Each Android app lives in its own security sandbox, protected by
+ the following Android security features: </p>
<ul>
<li>The Android operating system is a multi-user Linux system in which each app is a
@@ -40,54 +41,61 @@
<li>Each process has its own virtual machine (VM), so an app's code runs in isolation from
other apps.</li>
-<li>By default, every app runs in its own Linux process. Android starts the process when any
-of the app's components need to be executed, then shuts down the process when it's no longer
+<li>By default, every app runs in its own Linux process. The Android system starts
+ the process when any
+of the app's components need to be executed, and then shuts down the process
+ when it's no longer
needed or when the system must recover memory for other apps.</li>
</ul>
-<p>In this way, the Android system implements the <em>principle of least privilege</em>. That is,
+<p>The Android system implements the <em>principle of least privilege</em>. That is,
each app, by default, has access only to the components that it requires to do its work and
no more. This creates a very secure environment in which an app cannot access parts of
-the system for which it is not given permission.</p>
-
-<p>However, there are ways for an app to share data with other apps and for an
+the system for which it is not given permission. However, there are ways for an app to share
+ data with other apps and for an
app to access system services:</p>
<ul>
<li>It's possible to arrange for two apps to share the same Linux user ID, in which case
they are able to access each other's files. To conserve system resources, apps with the
-same user ID can also arrange to run in the same Linux process and share the same VM (the
-apps must also be signed with the same certificate).</li>
+same user ID can also arrange to run in the same Linux process and share the same VM. The
+apps must also be signed with the same certificate.</li>
<li>An app can request permission to access device data such as the user's
-contacts, SMS messages, the mountable storage (SD card), camera, Bluetooth, and more. The user has
+contacts, SMS messages, the mountable storage (SD card), camera, and Bluetooth. The user has
to explicitly grant these permissions. For more information, see
<a href="{@docRoot}training/permissions/index.html">Working with System Permissions</a>.</li>
</ul>
-<p>That covers the basics regarding how an Android app exists within the system. The rest of
-this document introduces you to:</p>
+<p>The rest of this document introduces the following concepts:</p>
<ul>
<li>The core framework components that define your app.</li>
- <li>The manifest file in which you declare components and required device features for your
+ <li>The manifest file in which you declare the components and the required device
+ features for your
app.</li>
- <li>Resources that are separate from the app code and allow your app to
+ <li>Resources that are separate from the app code and that allow your app to
gracefully optimize its behavior for a variety of device configurations.</li>
</ul>
-<h2 id="Components">App Components</h2>
+<h2 id="Components">App components</h2>
<p>App components are the essential building blocks of an Android app. Each
component is a different point through which the system can enter your app. Not all
-components are actual entry points for the user and some depend on each other, but each one exists
-as its own entity and plays a specific role—each one is a unique building block that
-helps define your app's overall behavior.</p>
+components are actual entry points for the user and some depend on each other,
+ but each one exists
+as its own entity and plays a specific role.</p>
-<p>There are four different types of app components. Each type serves a distinct purpose
-and has a distinct lifecycle that defines how the component is created and destroyed.</p>
-
-<p>Here are the four types of app components:</p>
+<p>There are four different types of app components:
+<ul>
+<li>Activities.</li>
+<li>Services.</li>
+<li>Content providers.</li>
+<li>Broadcast receivers.</li>
+</ul></p>
+Each type serves a distinct purpose
+and has a distinct lifecycle that defines how the component is created and destroyed.
+ The following sections describe the four types of app components.</p>
<dl>
@@ -98,11 +106,12 @@
emails, another activity to compose an email, and another activity for reading emails. Although
the activities work together to form a cohesive user experience in the email app, each one
is independent of the others. As such, a different app can start any one of these
-activities (if the email app allows it). For example, a camera app can start the
-activity in the email app that composes new mail, in order for the user to share a picture.
+activities if the email app allows it. For example, a camera app can start the
+activity in the email app that composes new mail to allow the user to share a picture.
-<p>An activity is implemented as a subclass of {@link android.app.Activity} and you can learn more
-about it in the <a href="{@docRoot}guide/components/activities.html">Activities</a>
+<p>An activity is implemented as a subclass of {@link android.app.Activity}. You can learn more
+about {@link android.app.Activity} in the
+ <a href="{@docRoot}guide/components/activities.html">Activities</a>
developer guide.</p>
</dd>
@@ -111,13 +120,16 @@
<dd>A <i>service</i> is a component that runs in the background to perform long-running
operations or to perform work for remote processes. A service
-does not provide a user interface. For example, a service might play music in the background while
+does not provide a user interface. For example, a service might play music in the
+ background while
the user is in a different app, or it might fetch data over the network without
-blocking user interaction with an activity. Another component, such as an activity, can start the
+blocking user interaction with an activity. Another component, such as an activity,
+ can start the
service and let it run or bind to it in order to interact with it.
-<p>A service is implemented as a subclass of {@link android.app.Service} and you can learn more
-about it in the <a href="{@docRoot}guide/components/services.html">Services</a> developer
+<p>A service is implemented as a subclass of {@link android.app.Service}. You can learn more
+about {@link android.app.Service} in the <a href="{@docRoot}guide/components/services.html">
+Services</a> developer
guide.</p>
</dd>
@@ -125,12 +137,14 @@
<dt><b>Content providers</b></dt>
<dd>A <i>content provider</i> manages a shared set of app data. You can store the data in
-the file system, an SQLite database, on the web, or any other persistent storage location your
-app can access. Through the content provider, other apps can query or even modify
-the data (if the content provider allows it). For example, the Android system provides a content
+the file system, in a SQLite database, on the web, or on any other persistent storage
+ location that your
+app can access. Through the content provider, other apps can query or modify
+the data if the content provider allows it. For example, the Android system provides a content
provider that manages the user's contact information. As such, any app with the proper
-permissions can query part of the content provider (such as {@link
-android.provider.ContactsContract.Data}) to read and write information about a particular person.
+permissions can query part of the content provider, such as {@link
+android.provider.ContactsContract.Data}, to read and write information about
+ a particular person.
<p>Content providers are also useful for reading and writing data that is private to your
app and not shared. For example, the <a
@@ -148,15 +162,17 @@
<dt><b>Broadcast receivers</b></dt>
<dd>A <i>broadcast receiver</i> is a component that responds to system-wide broadcast
-announcements. Many broadcasts originate from the system—for example, a broadcast announcing
+announcements. Many broadcasts originate from the system—for example,
+ a broadcast announcing
that the screen has turned off, the battery is low, or a picture was captured.
Apps can also initiate broadcasts—for example, to let other apps know that
-some data has been downloaded to the device and is available for them to use. Although broadcast
+some data has been downloaded to the device and is available for them to use.
+ Although broadcast
receivers don't display a user interface, they may <a
href="{@docRoot}guide/topics/ui/notifiers/notifications.html">create a status bar notification</a>
to alert the user when a broadcast event occurs. More commonly, though, a broadcast receiver is
-just a "gateway" to other components and is intended to do a very minimal amount of work. For
-instance, it might initiate a service to perform some work based on the event.
+just a <em>gateway</em> to other components and is intended to do a very minimal amount of work.
+ For instance, it might initiate a service to perform some work based on the event.
<p>A broadcast receiver is implemented as a subclass of {@link android.content.BroadcastReceiver}
and each broadcast is delivered as an {@link android.content.Intent} object. For more information,
@@ -170,52 +186,59 @@
<p>A unique aspect of the Android system design is that any app can start another
app’s component. For example, if you want the user to capture a
photo with the device camera, there's probably another app that does that and your
-app can use it, instead of developing an activity to capture a photo yourself. You don't
+app can use it instead of developing an activity to capture a photo yourself. You don't
need to incorporate or even link to the code from the camera app.
Instead, you can simply start the activity in the camera app that captures a
photo. When complete, the photo is even returned to your app so you can use it. To the user,
it seems as if the camera is actually a part of your app.</p>
-<p>When the system starts a component, it starts the process for that app (if it's not
-already running) and instantiates the classes needed for the component. For example, if your
+<p>When the system starts a component, it starts the process for that app if it's not
+already running and instantiates the classes needed for the component. For example, if your
app starts the activity in the camera app that captures a photo, that activity
runs in the process that belongs to the camera app, not in your app's process.
Therefore, unlike apps on most other systems, Android apps don't have a single entry
-point (there's no {@code main()} function, for example).</p>
+point (there's no {@code main()} function).</p>
<p>Because the system runs each app in a separate process with file permissions that
restrict access to other apps, your app cannot directly activate a component from
-another app. The Android system, however, can. So, to activate a component in
-another app, you must deliver a message to the system that specifies your <em>intent</em> to
+another app. However, the Android system can. To activate a component in
+another app, deliver a message to the system that specifies your <em>intent</em> to
start a particular component. The system then activates the component for you.</p>
-<h3 id="ActivatingComponents">Activating Components</h3>
+<h3 id="ActivatingComponents">Activating components</h3>
<p>Three of the four component types—activities, services, and
broadcast receivers—are activated by an asynchronous message called an <em>intent</em>.
-Intents bind individual components to each other at runtime (you can think of them
-as the messengers that request an action from other components), whether the component belongs
+Intents bind individual components to each other at runtime. You can think of them
+as the messengers that request an action from other components, whether the component belongs
to your app or another.</p>
-<p>An intent is created with an {@link android.content.Intent} object, which defines a message to
-activate either a specific component or a specific <em>type</em> of component—an intent
-can be either explicit or implicit, respectively.</p>
+<p class="note"><strong>Note:</strong> If your app targets Android 5.0 (API level 21) or later,
+ use the {@link android.app.job.JobScheduler} to execute background
+ services. For more information about using this class, see the
+ {@link android.app.job.JobScheduler} reference documentation.</p>
-<p>For activities and services, an intent defines the action to perform (for example, to "view" or
-"send" something) and may specify the URI of the data to act on (among other things that the
-component being started might need to know). For example, an intent might convey a request for an
+<p>An intent is created with an {@link android.content.Intent} object, which defines a message to
+activate either a specific component (explicit intent) or a specific <em>type</em> of component
+ (implicit intent).</p>
+
+<p>For activities and services, an intent defines the action to perform (for example, to
+ <em>view</em> or
+<em>send</em> something) and may specify the URI of the data to act on, among other things that the
+component being started might need to know. For example, an intent might convey a request for an
activity to show an image or to open a web page. In some cases, you can start an
-activity to receive a result, in which case, the activity also returns
-the result in an {@link android.content.Intent} (for example, you can issue an intent to let
-the user pick a personal contact and have it returned to you—the return intent includes a
-URI pointing to the chosen contact).</p>
+activity to receive a result, in which case the activity also returns
+the result in an {@link android.content.Intent}. For example, you can issue an intent to let
+the user pick a personal contact and have it returned to you. The return intent includes a
+URI pointing to the chosen contact.</p>
<p>For broadcast receivers, the intent simply defines the
-announcement being broadcast (for example, a broadcast to indicate the device battery is low
-includes only a known action string that indicates "battery is low").</p>
+announcement being broadcast. For example, a broadcast to indicate the device battery is low
+includes only a known action string that indicates <em>battery is low</em>.</p>
-<p>The other component type, content provider, is not activated by intents. Rather, it is
+<p>Unlike activities, services, and broadcast receivers, content providers are not activated
+ by intents. Rather, they are
activated when targeted by a request from a {@link android.content.ContentResolver}. The content
resolver handles all direct transactions with the content provider so that the component that's
performing transactions with the provider doesn't need to and instead calls methods on the {@link
@@ -224,15 +247,19 @@
<p>There are separate methods for activating each type of component:</p>
<ul>
- <li>You can start an activity (or give it something new to do) by
+ <li>You can start an activity or give it something new to do by
passing an {@link android.content.Intent} to {@link android.content.Context#startActivity
startActivity()} or {@link android.app.Activity#startActivityForResult startActivityForResult()}
(when you want the activity to return a result).</li>
- <li>You can start a service (or give new instructions to an ongoing service) by
+
+
+ <li>With Android 5.0 (API level 21) and later, you can start a service with
+ {@link android.app.job.JobScheduler}. For earlier Android versions, you can start
+ a service (or give new instructions to an ongoing service) by
passing an {@link android.content.Intent} to {@link android.content.Context#startService
-startService()}. Or you can bind to the service by passing an {@link android.content.Intent} to
-{@link android.content.Context#bindService bindService()}.</li>
- <li>You can initiate a broadcast by passing an {@link android.content.Intent} to methods like
+startService()}. You can bind to the service by passing an {@link android.content.Intent} to
+{@link android.content.Context#bindService bindService()}. </li>
+ <li>You can initiate a broadcast by passing an {@link android.content.Intent} to methods such as
{@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}, {@link
android.content.Context#sendOrderedBroadcast(Intent, String) sendOrderedBroadcast()}, or {@link
android.content.Context#sendStickyBroadcast sendStickyBroadcast()}.</li>
@@ -242,35 +269,35 @@
<p>For more information about using intents, see the <a
href="{@docRoot}guide/components/intents-filters.html">Intents and
-Intent Filters</a> document. More information about activating specific components is also provided
-in the following documents: <a
-href="{@docRoot}guide/components/activities.html">Activities</a>, <a
-href="{@docRoot}guide/components/services.html">Services</a>, {@link
-android.content.BroadcastReceiver} and <a
-href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a>.</p>
+Intent Filters</a> document.
+ The following documents provide more information about activating specifc components:
+ <a href="{@docRoot}guide/components/activities.html">Activities</a>,
+ <a href="{@docRoot}guide/components/services.html">Services
+ {@link android.content.BroadcastReceiver}, and
+ <a ref="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a>.</p>
-
-<h2 id="Manifest">The Manifest File</h2>
+<h2 id="Manifest">The manifest file</h2>
<p>Before the Android system can start an app component, the system must know that the
-component exists by reading the app's {@code AndroidManifest.xml} file (the "manifest"
-file). Your app must declare all its components in this file, which must be at the root of
-the app project directory.</p>
+component exists by reading the app's <em>manifest file</em>, {@code AndroidManifest.xml}.
+ Your app must declare all its components in this file, which must be at the root of the
+ app project directory.</p>
<p>The manifest does a number of things in addition to declaring the app's components,
-such as:</p>
+such as the following:</p>
<ul>
- <li>Identify any user permissions the app requires, such as Internet access or
+ <li>Identifies any user permissions the app requires, such as Internet access or
read-access to the user's contacts.</li>
- <li>Declare the minimum <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">API Level</a>
+ <li>Declares the minimum
+ <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">API Level</a>
required by the app, based on which APIs the app uses.</li>
- <li>Declare hardware and software features used or required by the app, such as a camera,
+ <li>Declares hardware and software features used or required by the app, such as a camera,
bluetooth services, or a multitouch screen.</li>
- <li>API libraries the app needs to be linked against (other than the Android framework
+ <li>Declares API libraries the app needs to be linked against (other than the Android framework
APIs), such as the <a
-href="http://code.google.com/android/add-ons/google-apis/maps-overview.html">Google Maps
-library</a>.</li>
- <li>And more</li>
+href="http://code.google.com/android/add-ons/google-apis/maps-overview.html">
+Google Maps library</a>.</li>
+
</ul>
@@ -301,47 +328,59 @@
android.app.Activity} subclass and the {@code android:label} attribute specifies a string
to use as the user-visible label for the activity.</p>
-<p>You must declare all app components this way:</p>
+<p>You must declare all app components using the following elements:</p>
<ul>
<li><code><a
href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> elements
-for activities</li>
+for activities.</li>
<li><code><a
href="{@docRoot}guide/topics/manifest/service-element.html"><service></a></code> elements for
-services</li>
+services.</li>
<li><code><a
href="{@docRoot}guide/topics/manifest/receiver-element.html"><receiver></a></code> elements
-for broadcast receivers</li>
+for broadcast receivers.</li>
<li><code><a
href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a></code> elements
-for content providers</li>
+for content providers.</li>
</ul>
<p>Activities, services, and content providers that you include in your source but do not declare
in the manifest are not visible to the system and, consequently, can never run. However,
broadcast
-receivers can be either declared in the manifest or created dynamically in code (as
-{@link android.content.BroadcastReceiver} objects) and registered with the system by calling
+receivers can be either declared in the manifest or created dynamically in code as
+{@link android.content.BroadcastReceiver} objects and registered with the system by calling
{@link android.content.Context#registerReceiver registerReceiver()}.</p>
<p>For more about how to structure the manifest file for your app, see <a
href="{@docRoot}guide/topics/manifest/manifest-intro.html">The AndroidManifest.xml File</a>
documentation. </p>
-
-
<h3 id="DeclaringComponentCapabilities">Declaring component capabilities</h3>
-<p>As discussed above, in <a href="#ActivatingComponents">Activating Components</a>, you can use an
-{@link android.content.Intent} to start activities, services, and broadcast receivers. You can do so
-by explicitly naming the target component (using the component class name) in the intent. However,
-the real power of intents lies in the concept of <em>implicit intents</em>. An implicit intent
-simply describes the type of action to perform (and, optionally, the data upon which you’d like to
-perform the action) and allows the system to find a component on the device that can perform the
-action and start it. If there are multiple components that can perform the action described by the
-intent, then the user selects which one to use.</p>
+<p>As discussed above, in <a href="#ActivatingComponents">Activating components</a>, you can use an
+{@link android.content.Intent} to start activities, services, and broadcast receivers.
-<p>The way the system identifies the components that can respond to an intent is by comparing the
+
+
+You can use an {@link android.content.Intent}
+ by explicitly naming the target component (using the component class name) in the intent.
+ You can also use an implicit intent, which
+describes the type of action to perform and, optionally, the data upon which you’d like to
+perform the action. The implicit intent allows the system to find a component on the device
+ that can perform the
+action and start it. If there are multiple components that can perform the action described by the
+intent, the user selects which one to use.</p>
+
+<p class="caution"><strong>Caution:</strong> If you use an intent to start a
+ {@link android.app.Service}, ensure that your app is secure by using an
+ <a href="{@docRoot}guide/components/intents-filters.html#Types">explicit</a>
+intent. Using an implicit intent to start a service is a
+security hazard because you cannot be certain what service will respond to the intent,
+and the user cannot see which service starts. Beginning with Android 5.0 (API level 21), the system
+throws an exception if you call {@link android.content.Context#bindService bindService()}
+with an implicit intent. Do not declare intent filters for your services. </p>
+
+<p>The system identifies the components that can respond to an intent by comparing the
intent received to the <i>intent filters</i> provided in the manifest file of other apps on
the device.</p>
@@ -351,8 +390,9 @@
adding an <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code
<intent-filter>}</a> element as a child of the component's declaration element.</p>
-<p>For example, if you've built an email app with an activity for composing a new email, you can
-declare an intent filter to respond to "send" intents (in order to send a new email) like this:</p>
+<p>For example, if you build an email app with an activity for composing a new email, you can
+declare an intent filter to respond to "send" intents (in order to send a new email),
+ as shown in the following example:</p>
<pre>
<manifest ... >
...
@@ -368,8 +408,9 @@
</manifest>
</pre>
-<p>Then, if another app creates an intent with the {@link
-android.content.Intent#ACTION_SEND} action and passes it to {@link android.app.Activity#startActivity
+<p>If another app creates an intent with the {@link
+android.content.Intent#ACTION_SEND} action and passes it to
+ {@link android.app.Activity#startActivity
startActivity()}, the system may start your activity so the user can draft and send an
email.</p>
@@ -382,7 +423,7 @@
<h3 id="DeclaringRequirements">Declaring app requirements</h3>
<p>There are a variety of devices powered by Android and not all of them provide the
-same features and capabilities. In order to prevent your app from being installed on devices
+same features and capabilities. To prevent your app from being installed on devices
that lack features needed by your app, it's important that you clearly define a profile for
the types of devices your app supports by declaring device and software requirements in your
manifest file. Most of these declarations are informational only and the system does not read
@@ -391,7 +432,7 @@
<p>For example, if your app requires a camera and uses APIs introduced in Android 2.1 (<a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">API Level</a> 7),
-you should declare these as requirements in your manifest file like this:</p>
+you must declare these as requirements in your manifest file as shown in the following example:</p>
<pre>
<manifest ... >
@@ -402,10 +443,10 @@
</manifest>
</pre>
-<p>Now, devices that do <em>not</em> have a camera and have an
-Android version <em>lower</em> than 2.1 cannot install your app from Google Play.</p>
-
-<p>However, you can also declare that your app uses the camera, but does not
+<p>With the declarations shown in the example, devices that do <em>not</em> have a
+ camera and have an
+Android version <em>lower</em> than 2.1 cannot install your app from Google Play.
+ However, you can declare that your app uses the camera, but does not
<em>require</em> it. In that case, your app must set the <a href=
"{@docRoot}guide/topics/manifest/uses-feature-element.html#required">{@code required}</a>
attribute to {@code "false"} and check at runtime whether
@@ -417,15 +458,15 @@
-<h2 id="Resources">App Resources</h2>
+<h2 id="Resources">App resources</h2>
<p>An Android app is composed of more than just code—it requires resources that are
separate from the source code, such as images, audio files, and anything relating to the visual
-presentation of the app. For example, you should define animations, menus, styles, colors,
+presentation of the app. For example, you can define animations, menus, styles, colors,
and the layout of activity user interfaces with XML files. Using app resources makes it easy
-to update various characteristics of your app without modifying code and—by providing
-sets of alternative resources—enables you to optimize your app for a variety of
-device configurations (such as different languages and screen sizes).</p>
+to update various characteristics of your app without modifying code. Providing
+sets of alternative resources enables you to optimize your app for a variety of
+device configurations, such as different languages and screen sizes.</p>
<p>For every resource that you include in your Android project, the SDK build tools define a unique
integer ID, which you can use to reference the resource from your app code or from
@@ -435,20 +476,22 @@
user interface.</p>
<p>One of the most important aspects of providing resources separate from your source code
-is the ability for you to provide alternative resources for different device
-configurations. For example, by defining UI strings in XML, you can translate the strings into other
-languages and save those strings in separate files. Then, based on a language <em>qualifier</em>
+is the ability to provide alternative resources for different device
+configurations. For example, by defining UI strings in XML, you can translate
+ the strings into other
+languages and save those strings in separate files. Then Android applies the
+ appropriate language strings
+to your UI based on a language <em>qualifier</em>
that you append to the resource directory's name (such as {@code res/values-fr/} for French string
-values) and the user's language setting, the Android system applies the appropriate language strings
-to your UI.</p>
+values) and the user's language setting.</p>
<p>Android supports many different <em>qualifiers</em> for your alternative resources. The
qualifier is a short string that you include in the name of your resource directories in order to
-define the device configuration for which those resources should be used. As another
-example, you should often create different layouts for your activities, depending on the
-device's screen orientation and size. For example, when the device screen is in portrait
+define the device configuration for which those resources should be used. For
+example, you should create different layouts for your activities, depending on the
+device's screen orientation and size. When the device screen is in portrait
orientation (tall), you might want a layout with buttons to be vertical, but when the screen is in
-landscape orientation (wide), the buttons should be aligned horizontally. To change the layout
+landscape orientation (wide), the buttons could be aligned horizontally. To change the layout
depending on the orientation, you can define two different layouts and apply the appropriate
qualifier to each layout's directory name. Then, the system automatically applies the appropriate
layout depending on the current device orientation.</p>
@@ -465,15 +508,15 @@
<dl>
<dt><a href="{@docRoot}guide/components/intents-filters.html">Intents and Intent Filters</a>
</dt>
- <dd>Information about how to use the {@link android.content.Intent} APIs to
+ <dd>How to use the {@link android.content.Intent} APIs to
activate app components, such as activities and services, and how to make your app components
available for use by other apps.</dd>
<dt><a href="{@docRoot}guide/components/activities.html">Activities</a></dt>
- <dd>Information about how to create an instance of the {@link android.app.Activity} class,
+ <dd>How to create an instance of the {@link android.app.Activity} class,
which provides a distinct screen in your application with a user interface.</dd>
<dt><a
href="{@docRoot}guide/topics/resources/providing-resources.html">Providing Resources</a></dt>
- <dd>Information about how Android apps are structured to separate app resources from the
+ <dd>How Android apps are structured to separate app resources from the
app code, including how you can provide alternative resources for specific device
configurations.
</dd>
@@ -484,14 +527,13 @@
<dl>
<dt><a href="{@docRoot}guide/practices/compatibility.html"
>Device Compatibility</a></dt>
- <dd>Information about Android works on different types of devices and an introduction
+ <dd>How Android works on different types of devices and an introduction
to how you can optimize your app for each device or restrict your app's availability
to different devices.</dd>
<dt><a href="{@docRoot}guide/topics/security/permissions.html"
>System Permissions</a></dt>
- <dd>Information about how Android restricts app access to certain APIs with a permission
+ <dd>How Android restricts app access to certain APIs with a permission
system that requires the user's consent for your app to use those APIs.</dd>
</dl>
</div>
</div>
-
diff --git a/docs/html/guide/components/intents-filters.jd b/docs/html/guide/components/intents-filters.jd
index d1d8c78..8f41bc3 100644
--- a/docs/html/guide/components/intents-filters.jd
+++ b/docs/html/guide/components/intents-filters.jd
@@ -7,21 +7,21 @@
<h2>In this document</h2>
<ol>
- <li><a href="#Types">Intent Types</a></li>
- <li><a href="#Building">Building an Intent</a>
+ <li><a href="#Types">Intent types</a></li>
+ <li><a href="#Building">Building an intent</a>
<ol>
<li><a href="#ExampleExplicit">Example explicit intent</a></li>
<li><a href="#ExampleSend">Example implicit intent</a></li>
<li><a href="#ForceChooser">Forcing an app chooser</a></li>
</ol>
</li>
- <li><a href="#Receiving">Receiving an Implicit Intent</a>
+ <li><a href="#Receiving">Receiving an implicit intent</a>
<ol>
<li><a href="#ExampleFilters">Example filters</a></li>
</ol>
</li>
- <li><a href="#PendingIntent">Using a Pending Intent</a></li>
- <li><a href="#Resolution">Intent Resolution</a>
+ <li><a href="#PendingIntent">Using a pending intent</a></li>
+ <li><a href="#Resolution">Intent resolution</a>
<ol>
<li><a href="#ActionTest">Action test</a></li>
<li><a href="#CategoryTest">Category test</a></li>
@@ -46,13 +46,14 @@
<p>An {@link android.content.Intent} is a messaging object you can use to request an action
from another <a href="{@docRoot}guide/components/fundamentals.html#Components">app component</a>.
Although intents facilitate communication between components in several ways, there are three
-fundamental use-cases:</p>
+fundamental use cases:</p>
<ul>
-<li><b>To start an activity:</b>
+<li><b>Starting an activity</b>
<p>An {@link android.app.Activity} represents a single screen in an app. You can start a new
instance of an {@link android.app.Activity} by passing an {@link android.content.Intent}
-to {@link android.content.Context#startActivity startActivity()}. The {@link android.content.Intent}
+to {@link android.content.Context#startActivity startActivity()}.
+ The {@link android.content.Intent}
describes the activity to start and carries any necessary data.</p>
<p>If you want to receive a result from the activity when it finishes,
@@ -63,10 +64,16 @@
For more information, see the <a
href="{@docRoot}guide/components/activities.html">Activities</a> guide.</p></li>
-<li><b>To start a service:</b>
+<li><b>Starting a service</b>
<p>A {@link android.app.Service} is a component that performs operations in the background
-without a user interface. You can start a service to perform a one-time operation
-(such as download a file) by passing an {@link android.content.Intent}
+without a user interface. With Android 5.0 (API level 21) and later, you can start a service
+ with {@link android.app.job.JobScheduler}. For more information
+ about {@link android.app.job.JobScheduler}, see its
+ {@link android.app.job.JobScheduler API-reference documentation}.</p>
+<p>For versions earlier than Android 5.0 (API level 21), you can start a service by using
+methods of the {@link android.app.Service} class. You can start a service
+ to perform a one-time operation
+(such as downloading a file) by passing an {@link android.content.Intent}
to {@link android.content.Context#startService startService()}. The {@link android.content.Intent}
describes the service to start and carries any necessary data.</p>
@@ -75,7 +82,7 @@
android.content.Context#bindService bindService()}</code>. For more information, see the <a
href="{@docRoot}guide/components/services.html">Services</a> guide.</p></li>
-<li><b>To deliver a broadcast:</b>
+<li><b>Delivering a broadcast</b>
<p>A broadcast is a message that any app can receive. The system delivers various
broadcasts for system events, such as when the system boots up or the device starts charging.
You can deliver a broadcast to other apps by passing an {@link android.content.Intent}
@@ -89,7 +96,7 @@
-<h2 id="Types">Intent Types</h2>
+<h2 id="Types">Intent types</h2>
<p>There are two types of intents:</p>
@@ -97,7 +104,7 @@
<li><b>Explicit intents</b> specify the component to start by name (the
fully-qualified class name). You'll typically use an explicit intent to start a component in
your own app, because you know the class name of the activity or service you want to start. For
-example, start a new activity in response to a user action or start a service to download
+example, you can start a new activity in response to a user action or start a service to download
a file in the background.</li>
<li><b>Implicit intents</b> do not name a specific component, but instead declare a general action
@@ -106,12 +113,13 @@
app show a specified location on a map.</li>
</ul>
-<p>When you create an explicit intent to start an activity or service, the system immediately
+<p>Figure 1 shows how an intent is delivered to start an activity. When you create an
+ explicit intent to start an activity or service, the system immediately
starts the app component specified in the {@link android.content.Intent} object.</p>
<div class="figure" style="width:446px">
<img src="{@docRoot}images/components/intent-filters@2x.png" width="446" alt=""/>
-<p class="img-caption"><strong>Figure 1.</strong> Illustration of how an implicit intent is
+<p class="img-caption"><strong>Figure 1.</strong> How an implicit intent is
delivered through the system to start another activity: <b>[1]</b> <em>Activity A</em> creates an
{@link android.content.Intent} with an action description and passes it to {@link
android.content.Context#startActivity startActivity()}. <b>[2]</b> The Android System searches all
@@ -135,11 +143,12 @@
Likewise, if you do <em>not</em> declare any intent filters for an activity, then it can be started
only with an explicit intent.</p>
-<p class="caution"><strong>Caution:</strong> To ensure your app is secure, always use an explicit
+<p class="caution"><strong>Caution:</strong> To ensure that your app is secure, always
+ use an explicit
intent when starting a {@link android.app.Service} and do not
declare intent filters for your services. Using an implicit intent to start a service is a
-security hazard because you cannot be certain what service will respond to the intent,
-and the user cannot see which service starts. Beginning with Android 5.0 (API level 21), the system
+security hazard because you can't be certain what service will respond to the intent,
+and the user can't see which service starts. Beginning with Android 5.0 (API level 21), the system
throws an exception if you call {@link android.content.Context#bindService bindService()}
with an implicit intent.</p>
@@ -147,7 +156,7 @@
-<h2 id="Building">Building an Intent</h2>
+<h2 id="Building">Building an intent</h2>
<p>An {@link android.content.Intent} object carries information that the Android system uses
to determine which component to start (such as the exact component name or component
@@ -163,22 +172,23 @@
<dd>The name of the component to start.
<p>This is optional, but it's the critical piece of information that makes an intent
-<b>explicit</b>, meaning that the intent should be delivered only to the app component
-defined by the component name. Without a component name, the intent is <b>implicit</b> and the
+<em>explicit</em>, meaning that the intent should be delivered only to the app component
+defined by the component name. Without a component name, the intent is <em>implicit</em> and the
system decides which component should receive the intent based on the other intent information
-(such as the action, data, and category—described below). So if you need to start a specific
+(such as the action, data, and category—described below). If you need to start a specific
component in your app, you should specify the component name.</p>
-<p class="note"><strong>Note:</strong> When starting a {@link android.app.Service}, you should
-<strong>always specify the component name</strong>. Otherwise, you cannot be certain what service
+<p class="note"><strong>Note:</strong> When starting a {@link android.app.Service},
+ <em>always specify the component name</em>. Otherwise, you cannot be certain what service
will respond to the intent, and the user cannot see which service starts.</p>
<p>This field of the {@link android.content.Intent} is a
{@link android.content.ComponentName} object, which you can specify using a fully
-qualified class name of the target component, including the package name of the app. For example,
+qualified class name of the target component, including the package name of the app, for example,
{@code com.example.ExampleActivity}. You can set the component name with {@link
android.content.Intent#setComponent setComponent()}, {@link android.content.Intent#setClass
-setClass()}, {@link android.content.Intent#setClassName(String, String) setClassName()}, or with the
+setClass()}, {@link android.content.Intent#setClassName(String, String) setClassName()},
+ or with the
{@link android.content.Intent} constructor.</p>
</dd>
@@ -188,10 +198,10 @@
<p>In the case of a broadcast intent, this is the action that took place and is being reported.
The action largely determines how the rest of the intent is structured—particularly
-what is contained in the data and extras.
+the information that is contained in the data and extras.
<p>You can specify your own actions for use by intents within your app (or for use by other
-apps to invoke components in your app), but you should usually use action constants
+apps to invoke components in your app), but you usually specify action constants
defined by the {@link android.content.Intent} class or other framework classes. Here are some
common actions for starting an activity:</p>
@@ -203,7 +213,7 @@
view in a map app.</dd>
<dt>{@link android.content.Intent#ACTION_SEND}</dt>
- <dd>Also known as the "share" intent, you should use this in an intent with {@link
+ <dd>Also known as the <em>share</em> intent, you should use this in an intent with {@link
android.content.Context#startActivity startActivity()} when you have some data that the user can
share through another app, such as an email app or social sharing app.</dd>
</dl>
@@ -217,12 +227,13 @@
setAction()} or with an {@link android.content.Intent} constructor.</p>
<p>If you define your own actions, be sure to include your app's package name
-as a prefix. For example:</p>
+as a prefix, as shown in the following example:</p>
<pre>static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";</pre>
</dd>
<dt><b>Data</b></dt>
-<dd>The URI (a {@link android.net.Uri} object) that references the data to be acted on and/or the
+<dd>The URI (a {@link android.net.Uri} object) that references the data to
+ be acted on and/or the
MIME type of that data. The type of data supplied is generally dictated by the intent's action. For
example, if the action is {@link android.content.Intent#ACTION_EDIT}, the data should contain the
URI of the document to edit.
@@ -231,10 +242,11 @@
it's often important to specify the type of data (its MIME type) in addition to its URI.
For example, an activity that's able to display images probably won't be able
to play an audio file, even though the URI formats could be similar.
-So specifying the MIME type of your data helps the Android
+Specifying the MIME type of your data helps the Android
system find the best component to receive your intent.
However, the MIME type can sometimes be inferred from the URI—particularly when the data is a
-{@code content:} URI, which indicates the data is located on the device and controlled by a
+{@code content:} URI. A {@code content:} URI indicates the data is located on the device
+ and controlled by a
{@link android.content.ContentProvider}, which makes the data MIME type visible to the system.</p>
<p>To set only the data URI, call {@link android.content.Intent#setData setData()}.
@@ -243,7 +255,7 @@
android.content.Intent#setDataAndType setDataAndType()}.</p>
<p class="caution"><strong>Caution:</strong> If you want to set both the URI and MIME type,
-<strong>do not</strong> call {@link android.content.Intent#setData setData()} and
+<em>don't</em> call {@link android.content.Intent#setData setData()} and
{@link android.content.Intent#setType setType()} because they each nullify the value of the other.
Always use {@link android.content.Intent#setDataAndType setDataAndType()} to set both
URI and MIME type.</p>
@@ -258,7 +270,7 @@
<dl>
<dt>{@link android.content.Intent#CATEGORY_BROWSABLE}</dt>
<dd>The target activity allows itself to be started by a web browser to display data
- referenced by a link—such as an image or an e-mail message.
+ referenced by a link, such as an image or an e-mail message.
</dd>
<dt>{@link android.content.Intent#CATEGORY_LAUNCHER}</dt>
<dd>The activity is the initial activity of a task and is listed in
@@ -276,14 +288,14 @@
<p>These properties listed above (component name, action, data, and category) represent the
defining characteristics of an intent. By reading these properties, the Android system
-is able to resolve which app component it should start.</p>
-
-<p>However, an intent can carry additional information that does not affect
-how it is resolved to an app component. An intent can also supply:</p>
+is able to resolve which app component it should start. However, an intent can carry
+ additional information that does not affect
+how it is resolved to an app component. An intent can also supply the following information:</p>
<dl>
<dt><b>Extras</b></dt>
-<dd>Key-value pairs that carry additional information required to accomplish the requested action.
+<dd>Key-value pairs that carry additional information required to accomplish
+ the requested action.
Just as some actions use particular kinds of data URIs, some actions also use particular extras.
<p>You can add extra data with various {@link android.content.Intent#putExtra putExtra()} methods,
@@ -293,21 +305,22 @@
android.content.Intent#putExtras putExtras()}.</p>
<p>For example, when creating an intent to send an email with
-{@link android.content.Intent#ACTION_SEND}, you can specify the "to" recipient with the
-{@link android.content.Intent#EXTRA_EMAIL} key, and specify the "subject" with the
+{@link android.content.Intent#ACTION_SEND}, you can specify the <em>to</em> recipient with the
+{@link android.content.Intent#EXTRA_EMAIL} key, and specify the <em>subject</em> with the
{@link android.content.Intent#EXTRA_SUBJECT} key.</p>
<p>The {@link android.content.Intent} class specifies many {@code EXTRA_*} constants
for standardized data types. If you need to declare your own extra keys (for intents that
your app receives), be sure to include your app's package name
-as a prefix. For example:</p>
+as a prefix, as shown in the following example:</p>
<pre>static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";</pre>
</dd>
<dt><b>Flags</b></dt>
-<dd>Flags defined in the {@link android.content.Intent} class that function as metadata for the
+<dd>Flags are defined in the {@link android.content.Intent} class that function as metadata for the
intent. The flags may instruct the Android system how to launch an activity (for example, which
-<a href="{@docRoot}guide/components/tasks-and-back-stack.html">task</a> the activity should belong
+<a href="{@docRoot}guide/components/tasks-and-back-stack.html">task</a>
+ the activity should belong
to) and how to treat it after it's launched (for example, whether it belongs in the list of recent
activities).
@@ -354,7 +367,8 @@
to perform the action. Using an implicit intent is useful when your app cannot perform the
action, but other apps probably can and you'd like the user to pick which app to use.</p>
-<p>For example, if you have content you want the user to share with other people, create an intent
+<p>For example, if you have content that you want the user to share with other people,
+ create an intent
with the {@link android.content.Intent#ACTION_SEND} action
and add extras that specify the content to share. When you call
{@link android.content.Context#startActivity startActivity()} with that intent, the user can
@@ -362,13 +376,15 @@
<p class="caution"><strong>Caution:</strong> It's possible that a user won't have <em>any</em>
apps that handle the implicit intent you send to {@link android.content.Context#startActivity
-startActivity()}. If that happens, the call will fail and your app will crash. To verify
+startActivity()}. If that happens, the call fails and your app crashes. To verify
that an activity will receive the intent, call {@link android.content.Intent#resolveActivity
resolveActivity()} on your {@link android.content.Intent} object. If the result is non-null,
-then there is at least one app that can handle the intent and it's safe to call
+ there is at least one app that can handle the intent and it's safe to call
{@link android.content.Context#startActivity startActivity()}. If the result is null,
-you should not use the intent and, if possible, you should disable the feature that issues
-the intent.</p>
+ do not use the intent and, if possible, you should disable the feature that issues
+the intent. The following example shows how to verify that the intent resolves
+to an activity. This example doesn't use a URI, but the intent's data type
+is declared to specify the content carried by the extras.</p>
<pre>
@@ -384,8 +400,7 @@
}
</pre>
-<p class="note"><strong>Note:</strong> In this case, a URI is not used, but the intent's data type
-is declared to specify the content carried by the extras.</p>
+
<p>When {@link android.content.Context#startActivity startActivity()} is called, the system
@@ -393,7 +408,7 @@
intent with the {@link android.content.Intent#ACTION_SEND} action and that carries "text/plain"
data). If there's only one app that can handle it, that app opens immediately and is given the
intent. If multiple activities accept the intent, the system
-displays a dialog so the user can pick which app to use..</p>
+displays a dialog such as the one shown in Figure 2, so the user can pick which app to use.</p>
<div class="figure" style="width:200px">
@@ -405,23 +420,26 @@
<p>When there is more than one app that responds to your implicit intent,
the user can select which app to use and make that app the default choice for the
-action. This is nice when performing an action for which the user
-probably wants to use the same app from now on, such as when opening a web page (users
-often prefer just one web browser) .</p>
+action. The ability to select a default is helpful when performing an action for which the user
+probably wants to use the same app every time, such as when opening a web page (users
+often prefer just one web browser).</p>
<p>However, if multiple apps can respond to the intent and the user might want to use a different
app each time, you should explicitly show a chooser dialog. The chooser dialog asks the
-user to select which app to use for the action every time (the user cannot select a default app for
+user to select which app to use for the action (the user cannot select a default app for
the action). For example, when your app performs "share" with the {@link
android.content.Intent#ACTION_SEND} action, users may want to share using a different app depending
-on their current situation, so you should always use the chooser dialog, as shown in figure 2.</p>
+on their current situation, so you should always use the chooser dialog, as shown in Figure 2.</p>
<p>To show the chooser, create an {@link android.content.Intent} using {@link
android.content.Intent#createChooser createChooser()} and pass it to {@link
-android.app.Activity#startActivity startActivity()}. For example:</p>
+android.app.Activity#startActivity startActivity()}, as shown in the following example.
+ This example displays a dialog with a list of apps that respond to the intent passed to the {@link
+android.content.Intent#createChooser createChooser()} method and uses the supplied text as the
+dialog title.</p>
<pre>
Intent sendIntent = new Intent(Intent.ACTION_SEND);
@@ -439,26 +457,16 @@
}
</pre>
-<p>This displays a dialog with a list of apps that respond to the intent passed to the {@link
-android.content.Intent#createChooser createChooser()} method and uses the supplied text as the
-dialog title.</p>
-
-
-
-
-
-
-
-<h2 id="Receiving">Receiving an Implicit Intent</h2>
+<h2 id="Receiving">Receiving an implicit intent</h2>
<p>To advertise which implicit intents your app can receive, declare one or more intent filters for
each of your app components with an <a href=
-"{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code <intent-filter>}</a>
+"{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code <intent-filter>}</a>
element in your <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest file</a>.
Each intent filter specifies the type of intents it accepts based on the intent's action,
-data, and category. The system will deliver an implicit intent to your app component only if the
+data, and category. The system delivers an implicit intent to your app component only if the
intent can pass through one of your intent filters.</p>
<p class="note"><strong>Note:</strong> An explicit intent is always delivered to its target,
@@ -471,28 +479,28 @@
in the {@link android.content.Intent} (such as to show the editor controls or not).</p>
<p>Each intent filter is defined by an <a
-href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code <intent-filter>}</a>
+href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code <intent-filter>}</a>
element in the app's manifest file, nested in the corresponding app component (such
-as an <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a>
+as an <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a>
element). Inside the <a
-href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code <intent-filter>}</a>,
+href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code <intent-filter>}</a>,
you can specify the type of intents to accept using one or more
of these three elements:</p>
<dl>
-<dt><a href="{@docRoot}guide/topics/manifest/action-element.html">{@code <action>}</a></dt>
+<dt><a href="{@docRoot}guide/topics/manifest/action-element.html">{@code <action>}</a></dt>
<dd>Declares the intent action accepted, in the {@code name} attribute. The value
must be the literal string value of an action, not the class constant.</dd>
-<dt><a href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a></dt>
+<dt><a href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a></dt>
<dd>Declares the type of data accepted, using one or more attributes that specify various
aspects of the data URI (<code>scheme</code>, <code>host</code>, <code>port</code>,
- <code>path</code>, etc.) and MIME type.</dd>
-<dt><a href="{@docRoot}guide/topics/manifest/category-element.html">{@code <category>}</a></dt>
+ <code>path</code>) and MIME type.</dd>
+<dt><a href="{@docRoot}guide/topics/manifest/category-element.html">{@code <category>}</a></dt>
<dd>Declares the intent category accepted, in the {@code name} attribute. The value
must be the literal string value of an action, not the class constant.
- <p class="note"><strong>Note:</strong> In order to receive implicit intents, you
- <strong>must include</strong> the
+ <p class="note"><strong>Note:</strong> To receive implicit intents, you
+ <em>must include</em> the
{@link android.content.Intent#CATEGORY_DEFAULT} category in the intent filter. The methods
{@link android.app.Activity#startActivity startActivity()} and
{@link android.app.Activity#startActivityForResult startActivityForResult()} treat all intents
@@ -515,12 +523,12 @@
</activity>
</pre>
-<p>It's okay to create a filter that includes more than one instance of
-<a href="{@docRoot}guide/topics/manifest/action-element.html">{@code <action>}</a>,
-<a href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a>, or
-<a href="{@docRoot}guide/topics/manifest/category-element.html">{@code <category>}</a>.
-If you do, you simply need to be certain that the component can handle any and all combinations
-of those filter elements.</p>
+<p>You can create a filter that includes more than one instance of
+<a href="{@docRoot}guide/topics/manifest/action-element.html">{@code <action>}</a>,
+<a href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a>, or
+<a href="{@docRoot}guide/topics/manifest/category-element.html">{@code <category>}</a>.
+If you do, you need to be certain that the component can handle any and all
+combinations of those filter elements.</p>
<p>When you want to handle multiple kinds of intents, but only in specific combinations of
action, data, and category type, then you need to create multiple intent filters.</p>
@@ -569,8 +577,8 @@
<h3 id="ExampleFilters">Example filters</h3>
-<p>To better understand some of the intent filter behaviors, look at the following snippet
-from the manifest file of a social-sharing app.</p>
+<p>To demonstrate some of the intent filter behaviors, here is an example
+from the manifest file of a social-sharing app:</p>
<pre>
<activity android:name="MainActivity">
@@ -607,9 +615,9 @@
indicates this is the main entry point and does not expect any intent data.</li>
<li>The {@link android.content.Intent#CATEGORY_LAUNCHER} category indicates that this activity's
icon should be placed in the system's app launcher. If the <a
- href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> element
+ href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> element
does not specify an icon with {@code icon}, then the system uses the icon from the <a
- href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a>
+ href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a>
element.</li>
</ul>
<p>These two must be paired together in order for the activity to appear in the app launcher.</p>
@@ -620,7 +628,7 @@
intent matching one of the two intent filters.</p>
<p class="note"><strong>Note:</strong> The MIME type,
-<a href="https://developers.google.com/panorama/android/">{@code
+<a href="https://developers.google.com/panorama/android/" class="external-link">{@code
application/vnd.google.panorama360+jpg}</a>, is a special data type that specifies
panoramic photos, which you can handle with the <a
href="{@docRoot}reference/com/google/android/gms/panorama/package-summary.html">Google
@@ -638,7 +646,7 @@
-<h2 id="PendingIntent">Using a Pending Intent</h2>
+<h2 id="PendingIntent">Using a pending intent</h2>
<p>A {@link android.app.PendingIntent} object is a wrapper around an {@link
android.content.Intent} object. The primary purpose of a {@link android.app.PendingIntent}
@@ -646,25 +654,25 @@
to use the contained {@link android.content.Intent} as if it were executed from your
app's own process.</p>
-<p>Major use cases for a pending intent include:</p>
+<p>Major use cases for a pending intent include the following:</p>
<ul>
- <li>Declare an intent to be executed when the user performs an action with your <a
+ <li>Declaring an intent to be executed when the user performs an action with your <a
href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Notification</a>
(the Android system's {@link android.app.NotificationManager}
executes the {@link android.content.Intent}).
- <li>Declare an intent to be executed when the user performs an action with your
+ <li>Declaring an intent to be executed when the user performs an action with your
<a href="{@docRoot}guide/topics/appwidgets/index.html">App Widget</a>
(the Home screen app executes the {@link android.content.Intent}).
- <li>Declare an intent to be executed at a specified time in the future (the Android
+ <li>Declaring an intent to be executed at a specified future time (the Android
system's {@link android.app.AlarmManager} executes the {@link android.content.Intent}).
</ul>
-<p>Because each {@link android.content.Intent} object is designed to be handled by a specific
+<p>Just as each {@link android.content.Intent} object is designed to be handled by a specific
type of app component (either an {@link android.app.Activity}, a {@link android.app.Service}, or
a {@link android.content.BroadcastReceiver}), so too must a {@link android.app.PendingIntent} be
-created with the same consideration. When using a pending intent, your app will not
+created with the same consideration. When using a pending intent, your app doesn't
execute the intent with a call such as {@link android.content.Context#startActivity
-startActivity()}. You must instead declare the intended component type when you create the
+startActivity()}. Instead, you must declare the intended component type when you create the
{@link android.app.PendingIntent} by calling the respective creator method:</p>
<ul>
@@ -677,14 +685,14 @@
</ul>
<p>Unless your app is <em>receiving</em> pending intents from other apps,
-the above methods to create a {@link android.app.PendingIntent} are the only
-{@link android.app.PendingIntent} methods you'll probably ever need.</p>
+the above methods to create a {@link android.app.PendingIntent} are probably the only
+{@link android.app.PendingIntent} methods you'll ever need.</p>
<p>Each method takes the current app {@link android.content.Context}, the
{@link android.content.Intent} you want to wrap, and one or more flags that specify
how the intent should be used (such as whether the intent can be used more than once).</p>
-<p>More information about using pending intents is provided with the documentation for each
+<p>For more information about using pending intents, see the documentation for each
of the respective use cases, such as in the <a
href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Notifications</a>
and <a href="{@docRoot}guide/topics/appwidgets/index.html">App Widgets</a> API guides.</p>
@@ -695,27 +703,27 @@
-<h2 id="Resolution">Intent Resolution</h2>
+<h2 id="Resolution">Intent resolution</h2>
<p>When the system receives an implicit intent to start an activity, it searches for the
-best activity for the intent by comparing the intent to intent filters based on three aspects:</p>
+best activity for the intent by comparing the it to intent filters based on three aspects:</p>
<ul>
- <li>The intent action
- <li>The intent data (both URI and data type)
- <li>The intent category
+ <li>Action.
+ <li>Data (both URI and data type).
+ <li>Category.
</ul>
-<p>The following sections describe how intents are matched to the appropriate component(s)
-in terms of how the intent filter is declared in an app's manifest file.</p>
+<p>The following sections describe how intents are matched to the appropriate components
+according to the intent filter declaration in an app's manifest file.</p>
<h3 id="ActionTest">Action test</h3>
<p>To specify accepted intent actions, an intent filter can declare zero or more
<a href="{@docRoot}guide/topics/manifest/action-element.html">{@code
-<action>}</a> elements. For example:</p>
+<action>}</a> elements, as shown in the following example:</p>
<pre>
<intent-filter>
@@ -725,13 +733,13 @@
</intent-filter>
</pre>
-<p>To get through this filter, the action specified in the {@link android.content.Intent}
+<p>To pass this filter, the action specified in the {@link android.content.Intent}
must match one of the actions listed in the filter.</p>
<p>If the filter does not list any actions, there is nothing for an
intent to match, so all intents fail the test. However, if an {@link android.content.Intent}
-does not specify an action, it will pass the test (as long as the filter
-contains at least one action).</p>
+does not specify an action, it passes the test as long as the filter
+contains at least one action.</p>
@@ -739,7 +747,7 @@
<p>To specify accepted intent categories, an intent filter can declare zero or more
<a href="{@docRoot}guide/topics/manifest/category-element.html">{@code
-<category>}</a> elements. For example:</p>
+<category>}</a> elements, as shown in the following example:</p>
<pre>
<intent-filter>
@@ -752,17 +760,17 @@
<p>For an intent to pass the category test, every category in the {@link android.content.Intent}
must match a category in the filter. The reverse is not necessary—the intent filter may
declare more categories than are specified in the {@link android.content.Intent} and the
-{@link android.content.Intent} will still pass. Therefore, an intent with no categories should
-always pass this test, regardless of what categories are declared in the filter.</p>
+{@link android.content.Intent} still passes. Therefore, an intent with no categories
+always passes this test, regardless of what categories are declared in the filter.</p>
<p class="note"><strong>Note:</strong>
-Android automatically applies the the {@link android.content.Intent#CATEGORY_DEFAULT} category
+Android automatically applies the {@link android.content.Intent#CATEGORY_DEFAULT} category
to all implicit intents passed to {@link
android.content.Context#startActivity startActivity()} and {@link
android.app.Activity#startActivityForResult startActivityForResult()}.
-So if you want your activity to receive implicit intents, it must
-include a category for {@code "android.intent.category.DEFAULT"} in its intent filters (as
-shown in the previous {@code <intent-filter>} example.</p>
+If you want your activity to receive implicit intents, it must
+include a category for {@code "android.intent.category.DEFAULT"} in its intent filters, as
+shown in the previous {@code <intent-filter>} example.</p>
@@ -770,7 +778,7 @@
<p>To specify accepted intent data, an intent filter can declare zero or more
<a href="{@docRoot}guide/topics/manifest/data-element.html">{@code
-<data>}</a> elements. For example:</p>
+<data>}</a> elements, as shown in the following example:</p>
<pre>
<intent-filter>
@@ -781,15 +789,16 @@
</pre>
<p>Each <code><a href="{@docRoot}guide/topics/manifest/data-element.html"><data></a></code>
-element can specify a URI structure and a data type (MIME media type). There are separate
-attributes — {@code scheme}, {@code host}, {@code port},
-and {@code path} — for each part of the URI:
+element can specify a URI structure and a data type (MIME media type).
+ Each part of the URI is a separate
+attribute: {@code scheme}, {@code host}, {@code port},
+and {@code path}:
</p>
-<p style="margin-left: 2em">{@code <scheme>://<host>:<port>/<path>}</p>
+<p style="margin-left: 2em">{@code <scheme>://<host>:<port>/<path>}</p>
<p>
-For example:
+The following example shows possible values for these attributes:
</p>
<p style="margin-left: 2em">{@code content://com.example.project:200/folder/subfolder/etc}</p>
@@ -799,7 +808,7 @@
</p>
<p>Each of these attributes is optional in a <a
-href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a> element,
+href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a> element,
but there are linear dependencies:</p>
<ul>
<li>If a scheme is not specified, the host is ignored.</li>
@@ -842,17 +851,17 @@
either if its URI matches a URI in the filter or if it has a {@code content:}
or {@code file:} URI and the filter does not specify a URI. In other words,
a component is presumed to support {@code content:} and {@code file:} data if
-its filter lists <em>only</em> a MIME type.</p></li>
+its filter lists <em>only</em> a MIME type.</li>
</ol>
<p>
This last rule, rule (d), reflects the expectation
that components are able to get local data from a file or content provider.
-Therefore, their filters can list just a data type and do not need to explicitly
+Therefore, their filters can list just a data type and don't need to explicitly
name the {@code content:} and {@code file:} schemes.
-This is a typical case. A <a
-href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a> element
-like the following, for example, tells Android that the component can get image data from a content
+The following example shows a typical case in which a <a
+href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a> element
+ tells Android that the component can get image data from a content
provider and display it:
</p>
@@ -863,14 +872,15 @@
</intent-filter></pre>
<p>
-Because most available data is dispensed by content providers, filters that
-specify a data type but not a URI are perhaps the most common.
+Filters that
+specify a data type but not a URI are perhaps the most common because most available
+ data is dispensed by content providers.
</p>
<p>
-Another common configuration is filters with a scheme and a data type. For
+Another common configuration is a filter with a scheme and a data type. For
example, a <a
-href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a>
+href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a>
element like the following tells Android that
the component can retrieve video data from the network in order to perform the action:
</p>
@@ -894,7 +904,7 @@
<p>Your application can use intent matching in a similar way.
The {@link android.content.pm.PackageManager} has a set of {@code query...()}
-methods that return all components that can accept a particular intent, and
+methods that return all components that can accept a particular intent and
a similar series of {@code resolve...()} methods that determine the best
component to respond to an intent. For example,
{@link android.content.pm.PackageManager#queryIntentActivities
@@ -907,7 +917,3 @@
{@link android.content.pm.PackageManager#queryBroadcastReceivers
queryBroadcastReceivers()}, for broadcast receivers.
</p>
-
-
-
-
diff --git a/docs/html/guide/components/services.jd b/docs/html/guide/components/services.jd
index e646a17..a7ed718 100644
--- a/docs/html/guide/components/services.jd
+++ b/docs/html/guide/components/services.jd
@@ -5,11 +5,11 @@
<ol id="qv">
<h2>In this document</h2>
<ol>
-<li><a href="#Basics">The Basics</a></li>
+<li><a href="#Basics">The basics</a></li>
<ol>
<li><a href="#Declaring">Declaring a service in the manifest</a></li>
</ol>
-<li><a href="#CreatingAService">Creating a Started Service</a>
+<li><a href="#CreatingAService">Creating a started service</a>
<ol>
<li><a href="#ExtendingIntentService">Extending the IntentService class</a></li>
<li><a href="#ExtendingService">Extending the Service class</a></li>
@@ -17,10 +17,10 @@
<li><a href="#Stopping">Stopping a service</a></li>
</ol>
</li>
-<li><a href="#CreatingBoundService">Creating a Bound Service</a></li>
-<li><a href="#Notifications">Sending Notifications to the User</a></li>
-<li><a href="#Foreground">Running a Service in the Foreground</a></li>
-<li><a href="#Lifecycle">Managing the Lifecycle of a Service</a>
+<li><a href="#CreatingBoundService">Creating a bound service</a></li>
+<li><a href="#Notifications">Sending notifications to the user</a></li>
+<li><a href="#Foreground">Running a service in the foreground</a></li>
+<li><a href="#Lifecycle">Managing the lifecycle of a service</a>
<ol>
<li><a href="#LifecycleCallbacks">Implementing the lifecycle callbacks</a></li>
</ol>
@@ -48,70 +48,80 @@
</div>
-
<p>A {@link android.app.Service} is an application component that can perform
-long-running operations in the background and does not provide a user interface. Another
-application component can start a service and it will continue to run in the background even if the
+long-running operations in the background, and it does not provide a user interface. Another
+application component can start a service, and it continues to run in the background even if the
user switches to another application. Additionally, a component can bind to a service to
-interact with it and even perform interprocess communication (IPC). For example, a service might
+interact with it and even perform interprocess communication (IPC). For example, a service can
handle network transactions, play music, perform file I/O, or interact with a content provider, all
from the background.</p>
-<p>A service can essentially take two forms:</p>
+<p>These are the three different types of services:</p>
<dl>
+ <dt>Scheduled</dt>
+ <dd>A service is <em>scheduled</em> when an API such as the {@link android.app.job.JobScheduler},
+ introduced in Android 5.0 (API level 21), launches the service. You can use the
+ {@link android.app.job.JobScheduler} by registering jobs and specifying their requirements for
+ network and timing. The system then gracefully schedules the jobs for execution at the
+ appropriate times. The {@link android.app.job.JobScheduler} provides many methods to define
+ service-execution conditions.
+ <p class="note"><strong>Note:</strong> If your app targets Android 5.0 (API level 21), Google
+ recommends that you use the {@link android.app.job.JobScheduler} to execute background
+ services. For more information about using this class, see the
+ {@link android.app.job.JobScheduler} reference documentation.</p></dd>
<dt>Started</dt>
- <dd>A service is "started" when an application component (such as an activity) starts it by
-calling {@link android.content.Context#startService startService()}. Once started, a service
-can run in the background indefinitely, even if the component that started it is destroyed. Usually,
-a started service performs a single operation and does not return a result to the caller.
-For example, it might download or upload a file over the network. When the operation is done, the
-service should stop itself.</dd>
+ <dd>A service is <em>started</em> when an application component (such as an activity)
+ calls {@link android.content.Context#startService startService()}. After it's started, a
+ service can run in the background indefinitely, even if the component that started it is
+ destroyed. Usually, a started service performs a single operation and does not return a result to
+ the caller. For example, it can download or upload a file over the network. When the operation is
+ complete, the service should stop itself.</dd>
<dt>Bound</dt>
- <dd>A service is "bound" when an application component binds to it by calling {@link
-android.content.Context#bindService bindService()}. A bound service offers a client-server
-interface that allows components to interact with the service, send requests, get results, and even
-do so across processes with interprocess communication (IPC). A bound service runs only as long as
-another application component is bound to it. Multiple components can bind to the service at once,
-but when all of them unbind, the service is destroyed.</dd>
+ <dd>A service is <em>bound</em> when an application component binds to it by calling {@link
+ android.content.Context#bindService bindService()}. A bound service offers a client-server
+ interface that allows components to interact with the service, send requests, receive results,
+ and even do so across processes with interprocess communication (IPC). A bound service runs only
+ as long as another application component is bound to it. Multiple components can bind to the
+ service at once, but when all of them unbind, the service is destroyed.</dd>
</dl>
-<p>Although this documentation generally discusses these two types of services separately, your
-service can work both ways—it can be started (to run indefinitely) and also allow binding.
-It's simply a matter of whether you implement a couple callback methods: {@link
+<p>Although this documentation generally discusses started and bound services separately,
+your service can work both ways—it can be started (to run indefinitely) and also allow
+binding. It's simply a matter of whether you implement a couple of callback methods: {@link
android.app.Service#onStartCommand onStartCommand()} to allow components to start it and {@link
android.app.Service#onBind onBind()} to allow binding.</p>
<p>Regardless of whether your application is started, bound, or both, any application component
-can use the service (even from a separate application), in the same way that any component can use
+can use the service (even from a separate application) in the same way that any component can use
an activity—by starting it with an {@link android.content.Intent}. However, you can declare
-the service as private, in the manifest file, and block access from other applications. This is
-discussed more in the section about <a href="#Declaring">Declaring the service in the
+the service as <em>private</em> in the manifest file and block access from other applications.
+This is discussed more in the section about <a href="#Declaring">Declaring the service in the
manifest</a>.</p>
<p class="caution"><strong>Caution:</strong> A service runs in the
-main thread of its hosting process—the service does <strong>not</strong> create its own thread
-and does <strong>not</strong> run in a separate process (unless you specify otherwise). This means
-that, if your service is going to do any CPU intensive work or blocking operations (such as MP3
-playback or networking), you should create a new thread within the service to do that work. By using
-a separate thread, you will reduce the risk of Application Not Responding (ANR) errors and the
-application's main thread can remain dedicated to user interaction with your activities.</p>
+main thread of its hosting process; the service does <strong>not</strong> create its own
+thread and does <strong>not</strong> run in a separate process unless you specify otherwise. If
+your service is going to perform any CPU-intensive work or blocking operations, such as MP3
+playback or networking, you should create a new thread within the service to complete that work.
+By using a separate thread, you can reduce the risk of Application Not Responding (ANR) errors,
+and the application's main thread can remain dedicated to user interaction with your
+activities.</p>
-
-<h2 id="Basics">The Basics</h2>
+<h2 id="Basics">The basics</h2>
<div class="sidebox-wrapper">
<div class="sidebox">
<h3>Should you use a service or a thread?</h3>
- <p>A service is simply a component that can run in the background even when the user is not
-interacting with your application. Thus, you should create a service only if that is what you
+ <p>A service is simply a component that can run in the background, even when the user is not
+interacting with your application, so you should create a service only if that is what you
need.</p>
- <p>If you need to perform work outside your main thread, but only while the user is interacting
-with your application, then you should probably instead create a new thread and not a service. For
-example, if you want to play some music, but only while your activity is running, you might create
+ <p>If you must perform work outside of your main thread, but only while the user is interacting
+with your application, you should instead create a new thread. For example, if you want to
+play some music, but only while your activity is running, you might create
a thread in {@link android.app.Activity#onCreate onCreate()}, start running it in {@link
-android.app.Activity#onStart onStart()}, then stop it in {@link android.app.Activity#onStop
-onStop()}. Also consider using {@link android.os.AsyncTask} or {@link android.os.HandlerThread},
+android.app.Activity#onStart onStart()}, and stop it in {@link android.app.Activity#onStop
+onStop()}. Also consider using {@link android.os.AsyncTask} or {@link android.os.HandlerThread}
instead of the traditional {@link java.lang.Thread} class. See the <a
href="{@docRoot}guide/components/processes-and-threads.html#Threads">Processes and
Threading</a> document for more information about threads.</p>
@@ -121,78 +131,81 @@
</div>
</div>
-<p>To create a service, you must create a subclass of {@link android.app.Service} (or one
-of its existing subclasses). In your implementation, you need to override some callback methods that
-handle key aspects of the service lifecycle and provide a mechanism for components to bind to
-the service, if appropriate. The most important callback methods you should override are:</p>
+<p>To create a service, you must create a subclass of {@link android.app.Service} or use one
+of its existing subclasses. In your implementation, you must override some callback methods that
+handle key aspects of the service lifecycle and provide a mechanism that allows the components to
+bind to the service, if appropriate. These are the most important callback methods that you should
+override:</p>
<dl>
<dt>{@link android.app.Service#onStartCommand onStartCommand()}</dt>
- <dd>The system calls this method when another component, such as an activity,
-requests that the service be started, by calling {@link android.content.Context#startService
-startService()}. Once this method executes, the service is started and can run in the
+ <dd>The system invokes this method by calling {@link android.content.Context#startService
+startService()} when another component (such as an activity) requests that the service be started.
+When this method executes, the service is started and can run in the
background indefinitely. If you implement this, it is your responsibility to stop the service when
-its work is done, by calling {@link android.app.Service#stopSelf stopSelf()} or {@link
-android.content.Context#stopService stopService()}. (If you only want to provide binding, you don't
-need to implement this method.)</dd>
+its work is complete by calling {@link android.app.Service#stopSelf stopSelf()} or {@link
+android.content.Context#stopService stopService()}. If you only want to provide binding, you don't
+need to implement this method.</dd>
<dt>{@link android.app.Service#onBind onBind()}</dt>
- <dd>The system calls this method when another component wants to bind with the
-service (such as to perform RPC), by calling {@link android.content.Context#bindService
-bindService()}. In your implementation of this method, you must provide an interface that clients
-use to communicate with the service, by returning an {@link android.os.IBinder}. You must always
-implement this method, but if you don't want to allow binding, then you should return null.</dd>
+ <dd>The system invokes this method by calling {@link android.content.Context#bindService
+bindService()} when another component wants to bind with the service (such as to perform RPC).
+In your implementation of this method, you must provide an interface that clients
+use to communicate with the service by returning an {@link android.os.IBinder}. You must always
+implement this method; however, if you don't want to allow binding, you should return
+null.</dd>
<dt>{@link android.app.Service#onCreate()}</dt>
- <dd>The system calls this method when the service is first created, to perform one-time setup
-procedures (before it calls either {@link android.app.Service#onStartCommand onStartCommand()} or
+ <dd>The system invokes this method to perform one-time setup procedures when the service is
+initially created (before it calls either
+{@link android.app.Service#onStartCommand onStartCommand()} or
{@link android.app.Service#onBind onBind()}). If the service is already running, this method is not
called.</dd>
<dt>{@link android.app.Service#onDestroy()}</dt>
- <dd>The system calls this method when the service is no longer used and is being destroyed.
+ <dd>The system invokes this method when the service is no longer used and is being destroyed.
Your service should implement this to clean up any resources such as threads, registered
-listeners, receivers, etc. This is the last call the service receives.</dd>
+listeners, or receivers. This is the last call that the service receives.</dd>
</dl>
<p>If a component starts the service by calling {@link
android.content.Context#startService startService()} (which results in a call to {@link
-android.app.Service#onStartCommand onStartCommand()}), then the service
-remains running until it stops itself with {@link android.app.Service#stopSelf()} or another
+android.app.Service#onStartCommand onStartCommand()}), the service
+continues to run until it stops itself with {@link android.app.Service#stopSelf()} or another
component stops it by calling {@link android.content.Context#stopService stopService()}.</p>
<p>If a component calls
-{@link android.content.Context#bindService bindService()} to create the service (and {@link
-android.app.Service#onStartCommand onStartCommand()} is <em>not</em> called), then the service runs
-only as long as the component is bound to it. Once the service is unbound from all clients, the
-system destroys it.</p>
+{@link android.content.Context#bindService bindService()} to create the service and {@link
+android.app.Service#onStartCommand onStartCommand()} is <em>not</em> called, the service runs
+only as long as the component is bound to it. After the service is unbound from all of its clients,
+the system destroys it.</p>
-<p>The Android system will force-stop a service only when memory is low and it must recover system
+<p>The Android system force-stops a service only when memory is low and it must recover system
resources for the activity that has user focus. If the service is bound to an activity that has user
-focus, then it's less likely to be killed, and if the service is declared to <a
-href="#Foreground">run in the foreground</a> (discussed later), then it will almost never be killed.
-Otherwise, if the service was started and is long-running, then the system will lower its position
-in the list of background tasks over time and the service will become highly susceptible to
-killing—if your service is started, then you must design it to gracefully handle restarts
+focus, it's less likely to be killed; if the service is declared to <a
+href="#Foreground">run in the foreground</a>, it's rarely killed.
+If the service is started and is long-running, the system lowers its position
+in the list of background tasks over time, and the service becomes highly susceptible to
+killing—if your service is started, you must design it to gracefully handle restarts
by the system. If the system kills your service, it restarts it as soon as resources become
-available again (though this also depends on the value you return from {@link
-android.app.Service#onStartCommand onStartCommand()}, as discussed later). For more information
+available, but this also depends on the value that you return from {@link
+android.app.Service#onStartCommand onStartCommand()}. For more information
about when the system might destroy a service, see the <a
href="{@docRoot}guide/components/processes-and-threads.html">Processes and Threading</a>
document.</p>
-<p>In the following sections, you'll see how you can create each type of service and how to use
-it from other application components.</p>
-
-
+<p>In the following sections, you'll see how you can create the
+{@link android.content.Context#startService startService()} and
+{@link android.content.Context#bindService bindService()} service methods, as well as how to use
+them from other application components.</p>
<h3 id="Declaring">Declaring a service in the manifest</h3>
-<p>Like activities (and other components), you must declare all services in your application's
-manifest file.</p>
+<p>You must declare all services in your application's
+manifest file, just as you do for activities and other components.</p>
<p>To declare your service, add a <a
-href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a> element
+href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a> element
as a child of the <a
-href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a>
-element. For example:</p>
+href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a>
+element. Here is an example:</p>
<pre>
<manifest ... >
@@ -205,48 +218,44 @@
</pre>
<p>See the <a
-href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a> element
+href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a> element
reference for more information about declaring your service in the manifest.</p>
-<p>There are other attributes you can include in the <a
-href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a> element to
-define properties such as permissions required to start the service and the process in
+<p>There are other attributes that you can include in the <a
+href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a> element to
+define properties such as the permissions that are required to start the service and the process in
which the service should run. The <a
href="{@docRoot}guide/topics/manifest/service-element.html#nm">{@code android:name}</a>
-attribute is the only required attribute—it specifies the class name of the service. Once
-you publish your application, you should not change this name, because if you do, you risk breaking
+attribute is the only required attribute—it specifies the class name of the service. After
+you publish your application, leave this name unchanged to avoid the risk of breaking
code due to dependence on explicit intents to start or bind the service (read the blog post, <a
href="http://android-developers.blogspot.com/2011/06/things-that-cannot-change.html">Things
That Cannot Change</a>).
-<p>To ensure your app is secure, <strong>always use an explicit intent when starting or binding
-your {@link android.app.Service}</strong> and do not declare intent filters for the service. If
-it's critical that you allow for some amount of ambiguity as to which service starts, you can
-supply intent filters for your services and exclude the component name from the {@link
-android.content.Intent}, but you then must set the package for the intent with {@link
-android.content.Intent#setPackage setPackage()}, which provides sufficient disambiguation for the
-target service.</p>
+<p class="caution"><strong>Caution</strong>: To ensure that your app is secure, always use an
+explicit intent when starting a {@link android.app.Service} and do not declare intent filters for
+your services. Using an implicit intent to start a service is a security hazard because you cannot
+be certain of the service that will respond to the intent, and the user cannot see which service
+starts. Beginning with Android 5.0 (API level 21), the system throws an exception if you call
+{@link android.content.Context#bindService bindService()} with an implicit intent.</p>
-<p>Additionally, you can ensure that your service is available to only your app by
+<p>You can ensure that your service is available to only your app by
including the <a
href="{@docRoot}guide/topics/manifest/service-element.html#exported">{@code android:exported}</a>
-attribute and setting it to {@code "false"}. This effectively stops other apps from starting your
+attribute and setting it to {@code false}. This effectively stops other apps from starting your
service, even when using an explicit intent.</p>
-
-
-
-<h2 id="CreatingStartedService">Creating a Started Service</h2>
+<h2 id="CreatingStartedService">Creating a started service</h2>
<p>A started service is one that another component starts by calling {@link
-android.content.Context#startService startService()}, resulting in a call to the service's
+android.content.Context#startService startService()}, which results in a call to the service's
{@link android.app.Service#onStartCommand onStartCommand()} method.</p>
<p>When a service is started, it has a lifecycle that's independent of the
-component that started it and the service can run in the background indefinitely, even if
+component that started it. The service can run in the background indefinitely, even if
the component that started it is destroyed. As such, the service should stop itself when its job
-is done by calling {@link android.app.Service#stopSelf stopSelf()}, or another component can stop it
-by calling {@link android.content.Context#stopService stopService()}.</p>
+is complete by calling {@link android.app.Service#stopSelf stopSelf()}, or another component can
+stop it by calling {@link android.content.Context#stopService stopService()}.</p>
<p>An application component such as an activity can start the service by calling {@link
android.content.Context#startService startService()} and passing an {@link android.content.Intent}
@@ -254,65 +263,65 @@
this {@link android.content.Intent} in the {@link android.app.Service#onStartCommand
onStartCommand()} method.</p>
-<p>For instance, suppose an activity needs to save some data to an online database. The activity can
-start a companion service and deliver it the data to save by passing an intent to {@link
+<p>For instance, suppose an activity needs to save some data to an online database. The activity
+can start a companion service and deliver it the data to save by passing an intent to {@link
android.content.Context#startService startService()}. The service receives the intent in {@link
-android.app.Service#onStartCommand onStartCommand()}, connects to the Internet and performs the
-database transaction. When the transaction is done, the service stops itself and it is
+android.app.Service#onStartCommand onStartCommand()}, connects to the Internet, and performs the
+database transaction. When the transaction is complete, the service stops itself and is
destroyed.</p>
<p class="caution"><strong>Caution:</strong> A service runs in the same process as the application
-in which it is declared and in the main thread of that application, by default. So, if your service
+in which it is declared and in the main thread of that application by default. If your service
performs intensive or blocking operations while the user interacts with an activity from the same
-application, the service will slow down activity performance. To avoid impacting application
-performance, you should start a new thread inside the service.</p>
+application, the service slows down activity performance. To avoid impacting application
+performance, start a new thread inside the service.</p>
<p>Traditionally, there are two classes you can extend to create a started service:</p>
+
<dl>
<dt>{@link android.app.Service}</dt>
- <dd>This is the base class for all services. When you extend this class, it's important that
-you create a new thread in which to do all the service's work, because the service uses your
-application's main thread, by default, which could slow the performance of any activity your
+ <dd>This is the base class for all services. When you extend this class, it's important to
+create a new thread in which the service can complete all of its work; the service uses your
+application's main thread by default, which can slow the performance of any activity that your
application is running.</dd>
<dt>{@link android.app.IntentService}</dt>
- <dd>This is a subclass of {@link android.app.Service} that uses a worker thread to handle all
-start requests, one at a time. This is the best option if you don't require that your service
-handle multiple requests simultaneously. All you need to do is implement {@link
+ <dd>This is a subclass of {@link android.app.Service} that uses a worker thread to handle all of
+the start requests, one at a time. This is the best option if you don't require that your service
+handle multiple requests simultaneously. Implement {@link
android.app.IntentService#onHandleIntent onHandleIntent()}, which receives the intent for each
-start request so you can do the background work.</dd>
+start request so that you can complete the background work.</dd>
</dl>
<p>The following sections describe how you can implement your service using either one for these
classes.</p>
-
<h3 id="ExtendingIntentService">Extending the IntentService class</h3>
-<p>Because most started services don't need to handle multiple requests simultaneously
-(which can actually be a dangerous multi-threading scenario), it's probably best if you
+<p>Because most of the started services don't need to handle multiple requests simultaneously
+(which can actually be a dangerous multi-threading scenario), it's best that you
implement your service using the {@link android.app.IntentService} class.</p>
-<p>The {@link android.app.IntentService} does the following:</p>
+<p>The {@link android.app.IntentService} class does the following:</p>
<ul>
- <li>Creates a default worker thread that executes all intents delivered to {@link
-android.app.Service#onStartCommand onStartCommand()} separate from your application's main
+ <li>It creates a default worker thread that executes all of the intents that are delivered to
+{@link android.app.Service#onStartCommand onStartCommand()}, separate from your application's main
thread.</li>
<li>Creates a work queue that passes one intent at a time to your {@link
android.app.IntentService#onHandleIntent onHandleIntent()} implementation, so you never have to
worry about multi-threading.</li>
- <li>Stops the service after all start requests have been handled, so you never have to call
+ <li>Stops the service after all of the start requests are handled, so you never have to call
{@link android.app.Service#stopSelf}.</li>
- <li>Provides default implementation of {@link android.app.IntentService#onBind onBind()} that
-returns null.</li>
+ <li>Provides a default implementation of {@link android.app.IntentService#onBind onBind()}
+ that returns null.</li>
<li>Provides a default implementation of {@link android.app.IntentService#onStartCommand
onStartCommand()} that sends the intent to the work queue and then to your {@link
android.app.IntentService#onHandleIntent onHandleIntent()} implementation.</li>
</ul>
-<p>All this adds up to the fact that all you need to do is implement {@link
-android.app.IntentService#onHandleIntent onHandleIntent()} to do the work provided by the
-client. (Though, you also need to provide a small constructor for the service.)</p>
+<p>To complete the work that is provided by the client, implement {@link
+android.app.IntentService#onHandleIntent onHandleIntent()}.
+However, you also need to provide a small constructor for the service.</p>
<p>Here's an example implementation of {@link android.app.IntentService}:</p>
@@ -352,12 +361,12 @@
<p>If you decide to also override other callback methods, such as {@link
android.app.IntentService#onCreate onCreate()}, {@link
android.app.IntentService#onStartCommand onStartCommand()}, or {@link
-android.app.IntentService#onDestroy onDestroy()}, be sure to call the super implementation, so
+android.app.IntentService#onDestroy onDestroy()}, be sure to call the super implementation so
that the {@link android.app.IntentService} can properly handle the life of the worker thread.</p>
<p>For example, {@link android.app.IntentService#onStartCommand onStartCommand()} must return
-the default implementation (which is how the intent gets delivered to {@link
-android.app.IntentService#onHandleIntent onHandleIntent()}):</p>
+the default implementation, which is how the intent is delivered to {@link
+android.app.IntentService#onHandleIntent onHandleIntent()}:</p>
<pre>
@Override
@@ -369,22 +378,21 @@
<p>Besides {@link android.app.IntentService#onHandleIntent onHandleIntent()}, the only method
from which you don't need to call the super class is {@link android.app.IntentService#onBind
-onBind()} (but you only need to implement that if your service allows binding).</p>
+onBind()}. You need to implement this only if your service allows binding.</p>
<p>In the next section, you'll see how the same kind of service is implemented when extending
-the base {@link android.app.Service} class, which is a lot more code, but which might be
+the base {@link android.app.Service} class, which uses more code, but might be
appropriate if you need to handle simultaneous start requests.</p>
-
<h3 id="ExtendingService">Extending the Service class</h3>
-<p>As you saw in the previous section, using {@link android.app.IntentService} makes your
+<p>Using {@link android.app.IntentService} makes your
implementation of a started service very simple. If, however, you require your service to
-perform multi-threading (instead of processing start requests through a work queue), then you
+perform multi-threading (instead of processing start requests through a work queue), you
can extend the {@link android.app.Service} class to handle each intent.</p>
-<p>For comparison, the following example code is an implementation of the {@link
-android.app.Service} class that performs the exact same work as the example above using {@link
+<p>For comparison, the following example code shows an implementation of the {@link
+android.app.Service} class that performs the same work as the previous example using {@link
android.app.IntentService}. That is, for each start request, it uses a worker thread to perform the
job and processes only one request at a time.</p>
@@ -460,20 +468,20 @@
<p>However, because you handle each call to {@link android.app.Service#onStartCommand
onStartCommand()} yourself, you can perform multiple requests simultaneously. That's not what
-this example does, but if that's what you want, then you can create a new thread for each
-request and run them right away (instead of waiting for the previous request to finish).</p>
+this example does, but if that's what you want, you can create a new thread for each
+request and run them right away instead of waiting for the previous request to finish.</p>
<p>Notice that the {@link android.app.Service#onStartCommand onStartCommand()} method must return an
integer. The integer is a value that describes how the system should continue the service in the
-event that the system kills it (as discussed above, the default implementation for {@link
-android.app.IntentService} handles this for you, though you are able to modify it). The return value
+event that the system kills it. The default implementation for {@link
+android.app.IntentService} handles this for you, but you are able to modify it. The return value
from {@link android.app.Service#onStartCommand onStartCommand()} must be one of the following
constants:</p>
<dl>
<dt>{@link android.app.Service#START_NOT_STICKY}</dt>
<dd>If the system kills the service after {@link android.app.Service#onStartCommand
-onStartCommand()} returns, <em>do not</em> recreate the service, unless there are pending
+onStartCommand()} returns, <em>do not</em> recreate the service unless there are pending
intents to deliver. This is the safest option to avoid running your service when not necessary
and when your application can simply restart any unfinished jobs.</dd>
<dt>{@link android.app.Service#START_STICKY}</dt>
@@ -481,9 +489,9 @@
onStartCommand()} returns, recreate the service and call {@link
android.app.Service#onStartCommand onStartCommand()}, but <em>do not</em> redeliver the last intent.
Instead, the system calls {@link android.app.Service#onStartCommand onStartCommand()} with a
-null intent, unless there were pending intents to start the service, in which case,
+null intent unless there are pending intents to start the service. In that case,
those intents are delivered. This is suitable for media players (or similar services) that are not
-executing commands, but running indefinitely and waiting for a job.</dd>
+executing commands but are running indefinitely and waiting for a job.</dd>
<dt>{@link android.app.Service#START_REDELIVER_INTENT}</dt>
<dd>If the system kills the service after {@link android.app.Service#onStartCommand
onStartCommand()} returns, recreate the service and call {@link
@@ -494,35 +502,35 @@
<p>For more details about these return values, see the linked reference documentation for each
constant.</p>
-
-
-<h3 id="StartingAService">Starting a Service</h3>
+<h3 id="StartingAService">Starting a service</h3>
<p>You can start a service from an activity or other application component by passing an
{@link android.content.Intent} (specifying the service to start) to {@link
android.content.Context#startService startService()}. The Android system calls the service's {@link
android.app.Service#onStartCommand onStartCommand()} method and passes it the {@link
-android.content.Intent}. (You should never call {@link android.app.Service#onStartCommand
-onStartCommand()} directly.)</p>
+android.content.Intent}.
+
+<p class="note"><strong>Note</strong>: Never call
+{@link android.app.Service#onStartCommand onStartCommand()} directly.</p>
<p>For example, an activity can start the example service in the previous section ({@code
HelloService}) using an explicit intent with {@link android.content.Context#startService
-startService()}:</p>
+startService()}, as shown here:</p>
<pre>
Intent intent = new Intent(this, HelloService.class);
startService(intent);
</pre>
-<p>The {@link android.content.Context#startService startService()} method returns immediately and
+<p>The {@link android.content.Context#startService startService()} method returns immediately, and
the Android system calls the service's {@link android.app.Service#onStartCommand
onStartCommand()} method. If the service is not already running, the system first calls {@link
-android.app.Service#onCreate onCreate()}, then calls {@link android.app.Service#onStartCommand
-onStartCommand()}.</p>
+android.app.Service#onCreate onCreate()}, and then it calls
+{@link android.app.Service#onStartCommand onStartCommand()}.</p>
-<p>If the service does not also provide binding, the intent delivered with {@link
+<p>If the service does not also provide binding, the intent that is delivered with {@link
android.content.Context#startService startService()} is the only mode of communication between the
-application component and the service. However, if you want the service to send a result back, then
+application component and the service. However, if you want the service to send a result back,
the client that starts the service can create a {@link android.app.PendingIntent} for a broadcast
(with {@link android.app.PendingIntent#getBroadcast getBroadcast()}) and deliver it to the service
in the {@link android.content.Intent} that starts the service. The service can then use the
@@ -533,109 +541,102 @@
the service (with {@link android.app.Service#stopSelf stopSelf()} or {@link
android.content.Context#stopService stopService()}) is required to stop it.</p>
-
<h3 id="Stopping">Stopping a service</h3>
<p>A started service must manage its own lifecycle. That is, the system does not stop or
destroy the service unless it must recover system memory and the service
-continues to run after {@link android.app.Service#onStartCommand onStartCommand()} returns. So,
-the service must stop itself by calling {@link android.app.Service#stopSelf stopSelf()} or another
+continues to run after {@link android.app.Service#onStartCommand onStartCommand()} returns. The
+service must stop itself by calling {@link android.app.Service#stopSelf stopSelf()}, or another
component can stop it by calling {@link android.content.Context#stopService stopService()}.</p>
<p>Once requested to stop with {@link android.app.Service#stopSelf stopSelf()} or {@link
android.content.Context#stopService stopService()}, the system destroys the service as soon as
possible.</p>
-<p>However, if your service handles multiple requests to {@link
-android.app.Service#onStartCommand onStartCommand()} concurrently, then you shouldn't stop the
-service when you're done processing a start request, because you might have since received a new
+<p>If your service handles multiple requests to {@link
+android.app.Service#onStartCommand onStartCommand()} concurrently, you shouldn't stop the
+service when you're done processing a start request, as you might have received a new
start request (stopping at the end of the first request would terminate the second one). To avoid
this problem, you can use {@link android.app.Service#stopSelf(int)} to ensure that your request to
stop the service is always based on the most recent start request. That is, when you call {@link
android.app.Service#stopSelf(int)}, you pass the ID of the start request (the <code>startId</code>
delivered to {@link android.app.Service#onStartCommand onStartCommand()}) to which your stop request
-corresponds. Then if the service received a new start request before you were able to call {@link
-android.app.Service#stopSelf(int)}, then the ID will not match and the service will not stop.</p>
+corresponds. Then, if the service receives a new start request before you are able to call {@link
+android.app.Service#stopSelf(int)}, the ID does not match and the service does not stop.</p>
-<p class="caution"><strong>Caution:</strong> It's important that your application stops its services
-when it's done working, to avoid wasting system resources and consuming battery power. If necessary,
-other components can stop the service by calling {@link
+<p class="caution"><strong>Caution:</strong> To avoid wasting system resources and consuming
+battery power, ensure that your application stops its services when it's done working.
+If necessary, other components can stop the service by calling {@link
android.content.Context#stopService stopService()}. Even if you enable binding for the service,
-you must always stop the service yourself if it ever received a call to {@link
+you must always stop the service yourself if it ever receives a call to {@link
android.app.Service#onStartCommand onStartCommand()}.</p>
<p>For more information about the lifecycle of a service, see the section below about <a
href="#Lifecycle">Managing the Lifecycle of a Service</a>.</p>
-
-
-<h2 id="CreatingBoundService">Creating a Bound Service</h2>
+<h2 id="CreatingBoundService">Creating a bound service</h2>
<p>A bound service is one that allows application components to bind to it by calling {@link
-android.content.Context#bindService bindService()} in order to create a long-standing connection
-(and generally does not allow components to <em>start</em> it by calling {@link
-android.content.Context#startService startService()}).</p>
+android.content.Context#bindService bindService()} to create a long-standing connection.
+It generally doesn't allow components to <em>start</em> it by calling {@link
+android.content.Context#startService startService()}.</p>
-<p>You should create a bound service when you want to interact with the service from activities
+<p>Create a bound service when you want to interact with the service from activities
and other components in your application or to expose some of your application's functionality to
-other applications, through interprocess communication (IPC).</p>
+other applications through interprocess communication (IPC).</p>
-<p>To create a bound service, you must implement the {@link
+<p>To create a bound service, implement the {@link
android.app.Service#onBind onBind()} callback method to return an {@link android.os.IBinder} that
defines the interface for communication with the service. Other application components can then call
{@link android.content.Context#bindService bindService()} to retrieve the interface and
begin calling methods on the service. The service lives only to serve the application component that
-is bound to it, so when there are no components bound to the service, the system destroys it
-(you do <em>not</em> need to stop a bound service in the way you must when the service is started
-through {@link android.app.Service#onStartCommand onStartCommand()}).</p>
+is bound to it, so when there are no components bound to the service, the system destroys it.
+You do <em>not</em> need to stop a bound service in the same way that you must when the service is
+started through {@link android.app.Service#onStartCommand onStartCommand()}.</p>
-<p>To create a bound service, the first thing you must do is define the interface that specifies
-how a client can communicate with the service. This interface between the service
+<p>To create a bound service, you must define the interface that specifies how a client can
+communicate with the service. This interface between the service
and a client must be an implementation of {@link android.os.IBinder} and is what your service must
return from the {@link android.app.Service#onBind
-onBind()} callback method. Once the client receives the {@link android.os.IBinder}, it can begin
+onBind()} callback method. After the client receives the {@link android.os.IBinder}, it can begin
interacting with the service through that interface.</p>
-<p>Multiple clients can bind to the service at once. When a client is done interacting with the
-service, it calls {@link android.content.Context#unbindService unbindService()} to unbind. Once
-there are no clients bound to the service, the system destroys the service.</p>
+<p>Multiple clients can bind to the service simultaneously. When a client is done interacting with
+the service, it calls {@link android.content.Context#unbindService unbindService()} to unbind.
+When there are no clients bound to the service, the system destroys the service.</p>
-<p>There are multiple ways to implement a bound service and the implementation is more
-complicated than a started service, so the bound service discussion appears in a separate
-document about <a
+<p>There are multiple ways to implement a bound service, and the implementation is more
+complicated than a started service. For these reasons, the bound service discussion appears in a
+separate document about <a
href="{@docRoot}guide/components/bound-services.html">Bound Services</a>.</p>
+<h2 id="Notifications">Sending notifications to the user</h2>
-
-<h2 id="Notifications">Sending Notifications to the User</h2>
-
-<p>Once running, a service can notify the user of events using <a
+<p>When a service is running, it can notify the user of events using <a
href="{@docRoot}guide/topics/ui/notifiers/toasts.html">Toast Notifications</a> or <a
href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>.</p>
-<p>A toast notification is a message that appears on the surface of the current window for a
-moment then disappears, while a status bar notification provides an icon in the status bar with a
+<p>A toast notification is a message that appears on the surface of the current window for only a
+moment before disappearing. A status bar notification provides an icon in the status bar with a
message, which the user can select in order to take an action (such as start an activity).</p>
-<p>Usually, a status bar notification is the best technique when some background work has completed
-(such as a file completed
-downloading) and the user can now act on it. When the user selects the notification from the
-expanded view, the notification can start an activity (such as to view the downloaded file).</p>
+<p>Usually, a status bar notification is the best technique to use when background work such as
+a file download has completed, and the user can now act on it. When the user
+selects the notification from the expanded view, the notification can start an activity
+(such as to display the downloaded file).</p>
<p>See the <a
href="{@docRoot}guide/topics/ui/notifiers/toasts.html">Toast Notifications</a> or <a
href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>
developer guides for more information.</p>
+<h2 id="Foreground">Running a service in the foreground</h2>
-
-<h2 id="Foreground">Running a Service in the Foreground</h2>
-
-<p>A foreground service is a service that's considered to be something the
-user is actively aware of and thus not a candidate for the system to kill when low on memory. A
+<p>A foreground service is a service that the
+user is actively aware of and is not a candidate for the system to kill when low on memory. A
foreground service must provide a notification for the status bar, which is placed under the
-"Ongoing" heading, which means that the notification cannot be dismissed unless the service is
-either stopped or removed from the foreground.</p>
+<em>Ongoing</em> heading. This means that the notification cannot be dismissed unless the service
+is either stopped or removed from the foreground.</p>
<p>For example, a music player that plays music from a service should be set to run in the
foreground, because the user is explicitly aware
@@ -643,9 +644,9 @@
the user to launch an activity to interact with the music player.</p>
<p>To request that your service run in the foreground, call {@link
-android.app.Service#startForeground startForeground()}. This method takes two parameters: an integer
-that uniquely identifies the notification and the {@link
-android.app.Notification} for the status bar. For example:</p>
+android.app.Service#startForeground startForeground()}. This method takes two parameters: an
+integer that uniquely identifies the notification and the {@link
+android.app.Notification} for the status bar. Here is an example:</p>
<pre>
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
@@ -657,30 +658,27 @@
startForeground(ONGOING_NOTIFICATION_ID, notification);
</pre>
-<p class="caution"><strong>Caution:</strong> The integer ID you give to {@link
+<p class="caution"><strong>Caution:</strong> The integer ID that you give to {@link
android.app.Service#startForeground startForeground()} must not be 0.</p>
-
<p>To remove the service from the foreground, call {@link
-android.app.Service#stopForeground stopForeground()}. This method takes a boolean, indicating
+android.app.Service#stopForeground stopForeground()}. This method takes a boolean, which indicates
whether to remove the status bar notification as well. This method does <em>not</em> stop the
-service. However, if you stop the service while it's still running in the foreground, then the
+service. However, if you stop the service while it's still running in the foreground, the
notification is also removed.</p>
<p>For more information about notifications, see <a
href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Creating Status Bar
Notifications</a>.</p>
+<h2 id="Lifecycle">Managing the lifecycle of a service</h2>
+<p>The lifecycle of a service is much simpler than that of an activity. However, it's even more
+important that you pay close attention to how your service is created and destroyed because a
+service can run in the background without the user being aware.</p>
-<h2 id="Lifecycle">Managing the Lifecycle of a Service</h2>
-
-<p>The lifecycle of a service is much simpler than that of an activity. However, it's even more important
-that you pay close attention to how your service is created and destroyed, because a service
-can run in the background without the user being aware.</p>
-
-<p>The service lifecycle—from when it's created to when it's destroyed—can follow two
-different paths:</p>
+<p>The service lifecycle—from when it's created to when it's destroyed—can follow
+either of these two paths:</p>
<ul>
<li>A started service
@@ -689,27 +687,26 @@
stop itself by calling {@link
android.app.Service#stopSelf() stopSelf()}. Another component can also stop the
service by calling {@link android.content.Context#stopService
-stopService()}. When the service is stopped, the system destroys it..</p></li>
+stopService()}. When the service is stopped, the system destroys it.</p></li>
<li>A bound service
<p>The service is created when another component (a client) calls {@link
android.content.Context#bindService bindService()}. The client then communicates with the service
through an {@link android.os.IBinder} interface. The client can close the connection by calling
{@link android.content.Context#unbindService unbindService()}. Multiple clients can bind to
-the same service and when all of them unbind, the system destroys the service. (The service
-does <em>not</em> need to stop itself.)</p></li>
+the same service and when all of them unbind, the system destroys the service. The service
+does <em>not</em> need to stop itself.</p></li>
</ul>
-<p>These two paths are not entirely separate. That is, you can bind to a service that was already
-started with {@link android.content.Context#startService startService()}. For example, a background
-music service could be started by calling {@link android.content.Context#startService
+<p>These two paths are not entirely separate. You can bind to a service that is already
+started with {@link android.content.Context#startService startService()}. For example, you can
+start a background music service by calling {@link android.content.Context#startService
startService()} with an {@link android.content.Intent} that identifies the music to play. Later,
possibly when the user wants to exercise some control over the player or get information about the
current song, an activity can bind to the service by calling {@link
-android.content.Context#bindService bindService()}. In cases like this, {@link
+android.content.Context#bindService bindService()}. In cases such as this, {@link
android.content.Context#stopService stopService()} or {@link android.app.Service#stopSelf
-stopSelf()} does not actually stop the service until all clients unbind. </p>
-
+stopSelf()} doesn't actually stop the service until all of the clients unbind.</p>
<h3 id="LifecycleCallbacks">Implementing the lifecycle callbacks</h3>
@@ -763,20 +760,30 @@
startService()} and the diagram on the right shows the lifecycle when the service is created
with {@link android.content.Context#bindService bindService()}.</p>
-<p>By implementing these methods, you can monitor two nested loops of the service's lifecycle: </p>
+<p>Figure 2 illustrates the typical callback methods for a service. Although the figure separates
+services that are created by {@link android.content.Context#startService startService()} from those
+created by {@link android.content.Context#bindService bindService()}, keep
+in mind that any service, no matter how it's started, can potentially allow clients to bind to it.
+A service that was initially started with {@link android.app.Service#onStartCommand
+onStartCommand()} (by a client calling {@link android.content.Context#startService startService()})
+can still receive a call to {@link android.app.Service#onBind onBind()} (when a client calls
+{@link android.content.Context#bindService bindService()}).</p>
+
+<p>By implementing these methods, you can monitor these two nested loops of the service's
+lifecycle:</p>
<ul>
-<li>The <strong>entire lifetime</strong> of a service happens between the time {@link
-android.app.Service#onCreate onCreate()} is called and the time {@link
+<li>The <strong>entire lifetime</strong> of a service occurs between the time that {@link
+android.app.Service#onCreate onCreate()} is called and the time that {@link
android.app.Service#onDestroy} returns. Like an activity, a service does its initial setup in
{@link android.app.Service#onCreate onCreate()} and releases all remaining resources in {@link
-android.app.Service#onDestroy onDestroy()}. For example, a
-music playback service could create the thread where the music will be played in {@link
-android.app.Service#onCreate onCreate()}, then stop the thread in {@link
+android.app.Service#onDestroy onDestroy()}. For example, a
+music playback service can create the thread where the music is played in {@link
+android.app.Service#onCreate onCreate()}, and then it can stop the thread in {@link
android.app.Service#onDestroy onDestroy()}.
-<p>The {@link android.app.Service#onCreate onCreate()} and {@link android.app.Service#onDestroy
-onDestroy()} methods are called for all services, whether
+<p class="note"><strong>Note</strong>: The {@link android.app.Service#onCreate onCreate()}
+and {@link android.app.Service#onDestroy onDestroy()} methods are called for all services, whether
they're created by {@link android.content.Context#startService startService()} or {@link
android.content.Context#bindService bindService()}.</p></li>
@@ -784,8 +791,8 @@
android.app.Service#onStartCommand onStartCommand()} or {@link android.app.Service#onBind onBind()}.
Each method is handed the {@link
android.content.Intent} that was passed to either {@link android.content.Context#startService
-startService()} or {@link android.content.Context#bindService bindService()}, respectively.
-<p>If the service is started, the active lifetime ends the same time that the entire lifetime
+startService()} or {@link android.content.Context#bindService bindService()}.
+<p>If the service is started, the active lifetime ends at the same time that the entire lifetime
ends (the service is still active even after {@link android.app.Service#onStartCommand
onStartCommand()} returns). If the service is bound, the active lifetime ends when {@link
android.app.Service#onUnbind onUnbind()} returns.</p>
@@ -795,26 +802,16 @@
<p class="note"><strong>Note:</strong> Although a started service is stopped by a call to
either {@link android.app.Service#stopSelf stopSelf()} or {@link
android.content.Context#stopService stopService()}, there is not a respective callback for the
-service (there's no {@code onStop()} callback). So, unless the service is bound to a client,
+service (there's no {@code onStop()} callback). Unless the service is bound to a client,
the system destroys it when the service is stopped—{@link
android.app.Service#onDestroy onDestroy()} is the only callback received.</p>
-<p>Figure 2 illustrates the typical callback methods for a service. Although the figure separates
-services that are created by {@link android.content.Context#startService startService()} from those
-created by {@link android.content.Context#bindService bindService()}, keep
-in mind that any service, no matter how it's started, can potentially allow clients to bind to it.
-So, a service that was initially started with {@link android.app.Service#onStartCommand
-onStartCommand()} (by a client calling {@link android.content.Context#startService startService()})
-can still receive a call to {@link android.app.Service#onBind onBind()} (when a client calls
-{@link android.content.Context#bindService bindService()}).</p>
-
<p>For more information about creating a service that provides binding, see the <a
href="{@docRoot}guide/components/bound-services.html">Bound Services</a> document,
which includes more information about the {@link android.app.Service#onRebind onRebind()}
callback method in the section about <a
-href="{@docRoot}guide/components/bound-services.html#Lifecycle">Managing the Lifecycle of
-a Bound Service</a>.</p>
-
+href="{@docRoot}guide/components/bound-services.html#Lifecycle">Managing the lifecycle of
+a bound service</a>.</p>
<!--
<h2>Beginner's Path</h2>
diff --git a/docs/html/guide/topics/renderscript/reference/rs_allocation_create.jd b/docs/html/guide/topics/renderscript/reference/rs_allocation_create.jd
new file mode 100644
index 0000000..2defca3
--- /dev/null
+++ b/docs/html/guide/topics/renderscript/reference/rs_allocation_create.jd
@@ -0,0 +1,1060 @@
+page.title=RenderScript Allocation Creation Functions
+
+@jd:body
+
+<div class='renderscript'>
+<h2>Overview</h2>
+<p> The functions below are used to create allocations from within a script.
+These functions can be called directly or indirectly from an invokable
+function. It is an error if any control flow can result in calling these functions
+from a RenderScript kernel function.
+</p>
+<h2>Summary</h2>
+<table class='jd-sumtable'><tbody>
+ <tr><th colspan='2'>Functions</th></tr>
+ <tr class='alt-color api apilevel-1'>
+ <td class='jd-linkcol'>
+ <a href='rs_allocation_create.html#android_rs:rsCreateAllocation'>rsCreateAllocation</a>
+ </td>
+ <td class='jd-descrcol' width='100%'>
+ Creates an <a href={@docRoot}guide/topics/renderscript/reference/rs_object_types.html#android_rs:rs_allocation>rs_allocation</a> object of given <a href={@docRoot}guide/topics/renderscript/reference/rs_object_types.html#android_rs:rs_type>rs_type</a>
+ </td>
+ </tr>
+ <tr class='alt-color api apilevel-1'>
+ <td class='jd-linkcol'>
+ <a href='rs_allocation_create.html#android_rs:rsCreateElement'>rsCreateElement</a>
+ </td>
+ <td class='jd-descrcol' width='100%'>
+ Creates an <a href={@docRoot}guide/topics/renderscript/reference/rs_object_types.html#android_rs:rs_element>rs_element</a> object of the specified data type
+ </td>
+ </tr>
+ <tr class='alt-color api apilevel-1'>
+ <td class='jd-linkcol'>
+ <a href='rs_allocation_create.html#android_rs:rsCreatePixelElement'>rsCreatePixelElement</a>
+ </td>
+ <td class='jd-descrcol' width='100%'>
+ Creates an <a href={@docRoot}guide/topics/renderscript/reference/rs_object_types.html#android_rs:rs_element>rs_element</a> object of the specified data type and data kind
+ </td>
+ </tr>
+ <tr class='alt-color api apilevel-1'>
+ <td class='jd-linkcol'>
+ <a href='rs_allocation_create.html#android_rs:rsCreateType'>rsCreateType</a>
+ </td>
+ <td class='jd-descrcol' width='100%'>
+ Creates an <a href={@docRoot}guide/topics/renderscript/reference/rs_object_types.html#android_rs:rs_type>rs_type</a> object with the specified <a href={@docRoot}guide/topics/renderscript/reference/rs_object_types.html#android_rs:rs_element>rs_element</a> and shape attributes
+ </td>
+ </tr>
+ <tr class='alt-color api apilevel-1'>
+ <td class='jd-linkcol'>
+ <a href='rs_allocation_create.html#android_rs:rsCreateVectorElement'>rsCreateVectorElement</a>
+ </td>
+ <td class='jd-descrcol' width='100%'>
+ Creates an <a href={@docRoot}guide/topics/renderscript/reference/rs_object_types.html#android_rs:rs_element>rs_element</a> object of the specified data type and vector width
+ </td>
+ </tr>
+</tbody></table>
+<h2>Functions</h2>
+<a name='android_rs:rsCreateAllocation'></a>
+<div class='jd-details'>
+ <h4 class='jd-details-title'>
+ <span class='sympad'>rsCreateAllocation</span>
+ <span class='normal'>: Creates an <a href={@docRoot}guide/topics/renderscript/reference/rs_object_types.html#android_rs:rs_allocation>rs_allocation</a> object of given <a href={@docRoot}guide/topics/renderscript/reference/rs_object_types.html#android_rs:rs_type>rs_type</a></span>
+ </h4>
+ <div class='jd-details-descr'>
+ <table class='jd-tagtable'><tbody>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation(<a href='rs_object_types.html#android_rs:rs_type'>rs_type</a> type);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation(<a href='rs_object_types.html#android_rs:rs_type'>rs_type</a> type, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> usage);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_char(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_char(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_char(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_char2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_char2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_char2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_char3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_char3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_char3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_char4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_char4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_char4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_double(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_double(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_double(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_double2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_double2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_double2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_double3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_double3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_double3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_double4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_double4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_double4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_float(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_float(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_float(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_float2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_float2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_float2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_float3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_float3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_float3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_float4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_float4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_float4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_half(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_half(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_half(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_half2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_half2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_half2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_half3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_half3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_half3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_half4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_half4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_half4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_int(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_int(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_int(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_int2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_int2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_int2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_int3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_int3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_int3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_int4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_int4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_int4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_long(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_long(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_long(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_long2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_long2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_long2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_long3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_long3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_long3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_long4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_long4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_long4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_short(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_short(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_short(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_short2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_short2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_short2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_short3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_short3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_short3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_short4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_short4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_short4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uchar(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uchar(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uchar(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uchar2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uchar2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uchar2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uchar3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uchar3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uchar3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uchar4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uchar4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uchar4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uint(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uint(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uint(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uint2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uint2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uint2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uint3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uint3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uint3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uint4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uint4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_uint4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ulong(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ulong(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ulong(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ulong2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ulong2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ulong2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ulong3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ulong3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ulong3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ulong4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ulong4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ulong4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ushort(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ushort(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ushort(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ushort2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ushort2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ushort2(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ushort3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ushort3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ushort3(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ushort4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ushort4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> rsCreateAllocation_ushort4(<a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ </tbody></table>
+ </div>
+ <div class='jd-tagdata'> <h5 class='jd-tagtitle'>Parameters</h5>
+ <table class='jd-tagtable'><tbody>
+ <tr><th>type</th><td>Type of the allocation</td></tr>
+ <tr><th>usage</th><td>How the allocation should be used. A valid value is either of the following or their combination:<br>RS_ALLOCATION_USAGE_GRAPHICS_TEXTURE,<br>RS_ALLOCATION_USAGE_SCRIPT.</td></tr>
+ <tr><th>mipmap</th><td>A boolean flag indicating if the allocation is mipmapped and has multiple levels of detail (LoD).</td></tr>
+ <tr><th>dimX</th><td>Size on dimension x. Must be non zero.</td></tr>
+ <tr><th>dimY</th><td>Size on dimension y. 0 for single-dimension allocations.</td></tr>
+ <tr><th>dimZ</th><td>Size on dimension z. 0 for single-dimension and two-dimension allocations.</td></tr>
+ </tbody></table>
+ </div>
+ <div class='jd-tagdata jd-tagdescr'>
+<p> Creates an rs_allocation object of the given rs_type and for the specified usages.
+</p>
+
+<p> RS_ALLOCATION_USAGE_SCRIPT and RS_ALLOCATION_USAGE_GRAPHICS_TEXTURE are the
+ only supported usage flags for Allocations created from within a RenderScript
+ script.
+</p>
+
+<p> You can also use rsCreateAllocation_<type><width> wrapper functions to directly
+ create allocations of scalar and vector numerical types without creating
+ intermediate rs_element or rs_type objects.
+</p>
+
+<p> For example, rsCreateAllocation_int4() returns an Allocation of int4 data type of
+ specified dimensions.
+</p>
+ </div>
+</div>
+
+<a name='android_rs:rsCreateElement'></a>
+<div class='jd-details'>
+ <h4 class='jd-details-title'>
+ <span class='sympad'>rsCreateElement</span>
+ <span class='normal'>: Creates an <a href={@docRoot}guide/topics/renderscript/reference/rs_object_types.html#android_rs:rs_element>rs_element</a> object of the specified data type</span>
+ </h4>
+ <div class='jd-details-descr'>
+ <table class='jd-tagtable'><tbody>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_element'>rs_element</a> rsCreateElement(<a href='rs_object_types.html#android_rs:rs_data_type'>rs_data_type</a> data_type);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ </tbody></table>
+ </div>
+ <div class='jd-tagdata'> <h5 class='jd-tagtitle'>Parameters</h5>
+ <table class='jd-tagtable'><tbody>
+ <tr><th>data_type</th><td>Data type of the Element</td></tr>
+ </tbody></table>
+ </div>
+ <div class='jd-tagdata jd-tagdescr'>
+<p> Creates an rs_element object of the specified data type. The data kind of
+ the element will be set to RS_KIND_USER and vector width will be set to 1,
+ indicating non-vector.
+</p>
+ </div>
+</div>
+
+<a name='android_rs:rsCreatePixelElement'></a>
+<div class='jd-details'>
+ <h4 class='jd-details-title'>
+ <span class='sympad'>rsCreatePixelElement</span>
+ <span class='normal'>: Creates an <a href={@docRoot}guide/topics/renderscript/reference/rs_object_types.html#android_rs:rs_element>rs_element</a> object of the specified data type and data kind</span>
+ </h4>
+ <div class='jd-details-descr'>
+ <table class='jd-tagtable'><tbody>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_element'>rs_element</a> rsCreatePixelElement(<a href='rs_object_types.html#android_rs:rs_data_type'>rs_data_type</a> data_type, <a href='rs_object_types.html#android_rs:rs_data_kind'>rs_data_kind</a> data_kind);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ </tbody></table>
+ </div>
+ <div class='jd-tagdata'> <h5 class='jd-tagtitle'>Parameters</h5>
+ <table class='jd-tagtable'><tbody>
+ <tr><th>data_type</th><td>Data type of the Element</td></tr>
+ <tr><th>data_kind</th><td>Data kind of the Element</td></tr>
+ </tbody></table>
+ </div>
+ <div class='jd-tagdata jd-tagdescr'>
+<p> Creates an rs_element object of the specified data type and data kind. The
+ vector width of the rs_element object will be set to 1, indicating non-vector.
+</p>
+ </div>
+</div>
+
+<a name='android_rs:rsCreateType'></a>
+<div class='jd-details'>
+ <h4 class='jd-details-title'>
+ <span class='sympad'>rsCreateType</span>
+ <span class='normal'>: Creates an <a href={@docRoot}guide/topics/renderscript/reference/rs_object_types.html#android_rs:rs_type>rs_type</a> object with the specified <a href={@docRoot}guide/topics/renderscript/reference/rs_object_types.html#android_rs:rs_element>rs_element</a> and shape attributes</span>
+ </h4>
+ <div class='jd-details-descr'>
+ <table class='jd-tagtable'><tbody>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_type'>rs_type</a> rsCreateType(<a href='rs_object_types.html#android_rs:rs_element'>rs_element</a> element, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_type'>rs_type</a> rsCreateType(<a href='rs_object_types.html#android_rs:rs_element'>rs_element</a> element, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_type'>rs_type</a> rsCreateType(<a href='rs_object_types.html#android_rs:rs_element'>rs_element</a> element, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_type'>rs_type</a> rsCreateType(<a href='rs_object_types.html#android_rs:rs_element'>rs_element</a> element, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimX, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimY, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> dimZ, bool mipmaps, bool faces, <a href='rs_object_types.html#android_rs:rs_yuv_format'>rs_yuv_format</a> yuv_format);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ </tbody></table>
+ </div>
+ <div class='jd-tagdata'> <h5 class='jd-tagtitle'>Parameters</h5>
+ <table class='jd-tagtable'><tbody>
+ <tr><th>element</th><td>An <a href={@docRoot}guide/topics/renderscript/reference/rs_object_types.html#android_rs:rs_element>rs_element</a> object that specifies the cell data type of an allocation.</td></tr>
+ <tr><th>dimX</th><td>Size on dimension x. Must be non zero.</td></tr>
+ <tr><th>dimY</th><td>Size on dimension y. 0 for single-dimension allocations.</td></tr>
+ <tr><th>dimZ</th><td>Size on dimension z. 0 for single-dimension and two-dimension allocations.</td></tr>
+ <tr><th>mipmaps</th><td>A boolean flag indicating if the allocation is mipmapped and has multiple levels of detail (LoD).</td></tr>
+ <tr><th>faces</th><td>A boolean flag indicating if the allocation is a cubemap that has cube faces.</td></tr>
+ <tr><th>yuv_format</th><td>Tye YUV layout.</td></tr>
+ </tbody></table>
+ </div>
+ <div class='jd-tagdata jd-tagdescr'>
+<p> Creates an rs_type object with the specified element and shape attributes.
+</p>
+
+<p> dimX specifies the size of the X dimension.
+</p>
+
+<p> dimY, if present and non-zero, indicates that the Y dimension is present and
+ indicates its size.
+</p>
+
+<p> dimZ, if present and non-zero, indicates that the Z dimension is present and
+ indicates its size.
+</p>
+
+<p> mipmaps indicates the presence of level of detail (LOD).
+</p>
+
+<p> faces indicates the presence of cubemap faces.
+</p>
+
+<p> yuv_format indicates the associated YUV format (or RS_YUV_NONE).
+</p>
+ </div>
+</div>
+
+<a name='android_rs:rsCreateVectorElement'></a>
+<div class='jd-details'>
+ <h4 class='jd-details-title'>
+ <span class='sympad'>rsCreateVectorElement</span>
+ <span class='normal'>: Creates an <a href={@docRoot}guide/topics/renderscript/reference/rs_object_types.html#android_rs:rs_element>rs_element</a> object of the specified data type and vector width</span>
+ </h4>
+ <div class='jd-details-descr'>
+ <table class='jd-tagtable'><tbody>
+ <tr>
+ <td><a href='rs_object_types.html#android_rs:rs_element'>rs_element</a> rsCreateVectorElement(<a href='rs_object_types.html#android_rs:rs_data_type'>rs_data_type</a> data_type, <a href='rs_value_types.html#android_rs:uint32_t'>uint32_t</a> vector_width);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ </tbody></table>
+ </div>
+ <div class='jd-tagdata'> <h5 class='jd-tagtitle'>Parameters</h5>
+ <table class='jd-tagtable'><tbody>
+ <tr><th>data_type</th><td>Data type of the Element</td></tr>
+ <tr><th>vector_width</th><td>Vector width</td></tr>
+ </tbody></table>
+ </div>
+ <div class='jd-tagdata jd-tagdescr'>
+<p> Creates an rs_element object of the specified data type and vector width.
+ Value of vector_width must be 2, 3 or 4. The data kind of the rs_element object will
+ be set to RS_KIND_USER.
+</p>
+ </div>
+</div>
+
+</div>
diff --git a/docs/html/guide/topics/renderscript/reference/rs_for_each.jd b/docs/html/guide/topics/renderscript/reference/rs_for_each.jd
index 9ba5614..8b19ba6e 100644
--- a/docs/html/guide/topics/renderscript/reference/rs_for_each.jd
+++ b/docs/html/guide/topics/renderscript/reference/rs_for_each.jd
@@ -1,10 +1,10 @@
-page.title=RenderScript Kernel Invocation Functions and Types
+page.title=RenderScript Kernel Launch Functions and Types
@jd:body
<div class='renderscript'>
<h2>Overview</h2>
-<p> The <a href='rs_for_each.html#android_rs:rsForEach'>rsForEach</a>() function can be used to invoke the root kernel of a script.
+<p> The <a href='rs_for_each.html#android_rs:rsForEach'>rsForEach</a>() and <a href='rs_for_each.html#android_rs:rsForEachWithOptions'>rsForEachWithOptions</a>() functions are used to launch foreach kernels.
</p>
<p> The other functions are used to get the characteristics of the invocation of
@@ -24,6 +24,14 @@
</tr>
<tr class='alt-color api apilevel-1'>
<td class='jd-linkcol'>
+ <a href='rs_for_each.html#android_rs:rs_kernel'>rs_kernel</a>
+ </td>
+ <td class='jd-descrcol' width='100%'>
+ Handle to a kernel function
+ </td>
+ </tr>
+ <tr class='alt-color api apilevel-1'>
+ <td class='jd-linkcol'>
<a href='rs_for_each.html#android_rs:rs_kernel_context'>rs_kernel_context</a>
</td>
<td class='jd-descrcol' width='100%'>
@@ -46,7 +54,15 @@
<a href='rs_for_each.html#android_rs:rsForEach'>rsForEach</a>
</td>
<td class='jd-descrcol' width='100%'>
- Invoke the root kernel of a script
+ Launches a kernel
+ </td>
+ </tr>
+ <tr class='alt-color api apilevel-1'>
+ <td class='jd-linkcol'>
+ <a href='rs_for_each.html#android_rs:rsForEachWithOptions'>rsForEachWithOptions</a>
+ </td>
+ <td class='jd-descrcol' width='100%'>
+ Launches a kernel with options
</td>
</tr>
<tr class='alt-color api apilevel-1'>
@@ -198,6 +214,21 @@
</div>
</div>
+<a name='android_rs:rs_kernel'></a>
+<div class='jd-details'>
+ <h4 class='jd-details-title'>
+ <span class='sympad'>rs_kernel</span>
+ <span class='normal'>: Handle to a kernel function</span>
+ </h4>
+ <div class='jd-details-descr'>
+<p>A typedef of: void* Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+</p>
+<p> An opaque type for a function that is defined with the kernel attribute. A value
+ of this type can be used in a <a href='rs_for_each.html#android_rs:rsForEach'>rsForEach</a> call to launch a kernel.
+</p>
+ </div>
+</div>
+
<a name='android_rs:rs_kernel_context'></a>
<div class='jd-details'>
<h4 class='jd-details-title'>
@@ -249,7 +280,7 @@
the cells.
</p>
-<p> The Start fields are inclusive and the End fields are exclusive. E.g. to iterate
+<p> The Start fields are inclusive and the End fields are exclusive. For example, to iterate
over cells 4, 5, 6, and 7 in the X dimension, set xStart to 4 and xEnd to 8.
</p>
</div>
@@ -260,14 +291,20 @@
<div class='jd-details'>
<h4 class='jd-details-title'>
<span class='sympad'>rsForEach</span>
- <span class='normal'>: Invoke the root kernel of a script</span>
+ <span class='normal'>: Launches a kernel</span>
</h4>
<div class='jd-details-descr'>
<table class='jd-tagtable'><tbody>
<tr>
+ <td>void rsForEach(<a href='rs_for_each.html#android_rs:rs_kernel'>rs_kernel</a> kernel, ... ...);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ <tr>
<td>void rsForEach(<a href='rs_object_types.html#android_rs:rs_script'>rs_script</a> script, <a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> input, <a href='rs_object_types.html#android_rs:rs_allocation'>rs_allocation</a> output);
</td>
- <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 14</a>
+ <td> <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 14 - 23</a>
</td>
</tr>
<tr>
@@ -300,35 +337,89 @@
<table class='jd-tagtable'><tbody>
<tr><th>script</th><td>Script to call.</td></tr>
<tr><th>input</th><td>Allocation to source data from.</td></tr>
- <tr><th>output</th><td>Allocation to write date into.</td></tr>
+ <tr><th>output</th><td>Allocation to write data into.</td></tr>
<tr><th>usrData</th><td>User defined data to pass to the script. May be NULL.</td></tr>
<tr><th>sc</th><td>Extra control information used to select a sub-region of the allocation to be processed or suggest a walking strategy. May be NULL.</td></tr>
<tr><th>usrDataLen</th><td>Size of the userData structure. This will be used to perform a shallow copy of the data if necessary.</td></tr>
+ <tr><th>kernel</th><td>Function designator of the kernel function to call, which must be defined with the kernel attribute.</td></tr>
+ <tr><th>...</th><td>Input and output allocations</td></tr>
</tbody></table>
</div>
<div class='jd-tagdata jd-tagdescr'>
-<p> Invoke the kernel named "root" of the specified script. Like other kernels, this root()
-function will be invoked repeatedly over the cells of the specificed allocation, filling
-the output allocation with the results.
+<p> Runs the kernel over zero or more input allocations. They are passed after the
+<a href='rs_for_each.html#android_rs:rs_kernel'>rs_kernel</a> argument. If the specified kernel returns a value, an output allocation
+must be specified as the last argument. All input allocations,
+and the output allocation if it exists, must have the same dimensions.
</p>
-<p> When rsForEach is called, the root script is launched immediately. rsForEach returns
-only when the script has completed and the output allocation is ready to use.
+<p> This is a synchronous function. A call to this function only returns after all
+the work has completed. If the kernel
+function returns any value, the call waits until all results have been written
+to the output allocation.
</p>
-<p> The rs_script argument is typically initialized using a global variable set from Java.
+<p> Up to API level 23, the kernel is implicitly specified as the kernel named
+"root" in the specified script, and only a single input allocation can be used.
+Starting in API level 24, an arbitrary kernel function can be used,
+as specified by the kernel argument.
+The kernel must be defined in the current script. In addition, more than one
+input can be used.
</p>
-<p> The kernel can be invoked with just an input allocation or just an output allocation.
-This can be done by defining an rs_allocation variable and not initializing it. E.g.<code><br/>
-rs_script gCustomScript;<br/>
-void specializedProcessing(rs_allocation in) {<br/>
- rs_allocation ignoredOut;<br/>
- rsForEach(gCustomScript, in, ignoredOut);<br/>
-}<br/></code>
+<p> For example,<code><br/>
+float __attribute__((kernel)) square(float a) {<br/>
+ return a * a;<br/>
+}<br/>
+<br/>
+void compute(rs_allocation ain, rs_allocation aout) {<br/>
+ rsForEach(square, ain, aout);<br/>
+}<br/>
+<br/></code>
+</p>
+ </div>
+</div>
+
+<a name='android_rs:rsForEachWithOptions'></a>
+<div class='jd-details'>
+ <h4 class='jd-details-title'>
+ <span class='sympad'>rsForEachWithOptions</span>
+ <span class='normal'>: Launches a kernel with options</span>
+ </h4>
+ <div class='jd-details-descr'>
+ <table class='jd-tagtable'><tbody>
+ <tr>
+ <td>void rsForEachWithOptions(<a href='rs_for_each.html#android_rs:rs_kernel'>rs_kernel</a> kernel, <a href='rs_for_each.html#android_rs:rs_script_call_t'>rs_script_call_t</a>* options, ... ...);
+</td>
+ <td> Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 24</a>
+ </td>
+ </tr>
+ </tbody></table>
+ </div>
+ <div class='jd-tagdata'> <h5 class='jd-tagtitle'>Parameters</h5>
+ <table class='jd-tagtable'><tbody>
+ <tr><th>kernel</th><td>Function designator to a function that is defined with the kernel attribute.</td></tr>
+ <tr><th>options</th><td>Launch options</td></tr>
+ <tr><th>...</th><td>Input and output allocations</td></tr>
+ </tbody></table>
+ </div>
+ <div class='jd-tagdata jd-tagdescr'>
+<p> Launches kernel in a way similar to <a href='rs_for_each.html#android_rs:rsForEach'>rsForEach</a>. However, instead of processing
+all cells in the input, this function only processes cells in the subspace of
+the index space specified in options. With the index space explicitly specified
+by options, no input or output allocation is required for a kernel launch using
+this API. If allocations are passed in, they must match the number of arguments
+and return value expected by the kernel function. The output allocation is
+present if and only if the kernel has a non-void return value.
</p>
-<p> If both input and output allocations are specified, they must have the same dimensions.
+<p> For example,<code><br/>
+ rs_script_call_t opts = {0};<br/>
+ opts.xStart = 0;<br/>
+ opts.xEnd = dimX;<br/>
+ opts.yStart = 0;<br/>
+ opts.yEnd = dimY / 2;<br/>
+ rsForEachWithOptions(foo, &opts, out, out);<br/>
+</code>
</p>
</div>
</div>
@@ -359,7 +450,7 @@
</p>
<p> You can access the kernel context by adding a special parameter named "context" of
-type rs_kernel_context to your kernel function. E.g.<br/>
+type rs_kernel_context to your kernel function. For example,<br/>
<code>short RS_KERNEL myKernel(short value, uint32_t x, rs_kernel_context context) {<br/>
// The current index in the common x, y, z dimensions are accessed by<br/>
// adding these variables as arguments. For the more rarely used indices<br/>
@@ -644,7 +735,7 @@
</p>
<p> You can access it by adding a special parameter named "context" of
-type rs_kernel_context to your kernel function. E.g.<br/>
+type rs_kernel_context to your kernel function. For example,<br/>
<code>int4 RS_KERNEL myKernel(int4 value, rs_kernel_context context) {<br/>
uint32_t size = rsGetDimX(context); //...<br/></code>
</p>
diff --git a/docs/html/samples/new/index.jd b/docs/html/samples/new/index.jd
index a7ffa8c..4d6262ed 100644
--- a/docs/html/samples/new/index.jd
+++ b/docs/html/samples/new/index.jd
@@ -5,7 +5,7 @@
<p>The following code samples were recently published. You can
download them in the Android SDK Manager under the <b>Samples for SDK</b>
-component for Android 6.0 (API 23).</p>
+component for Android 7.1 (API 25).</p>
<p class="note">
<strong>Note:</strong> The downloadable projects are designed
@@ -14,115 +14,67 @@
<!-- NOTE TO EDITORS: add most recent samples first -->
-<h3 id="ActiveNotification">
- <a href="{@docRoot}samples/ActiveNotifications/index.html">Active
- Notification</a>
-</h3>
-<p>
- This sample demonstrates how to use the {@link
- android.app.NotificationManager} to tell you how many notifications your app
- is currently showing.
-</p>
+<h3 id="app-shortcuts">App shortcuts sample</h3>
-<h3 id="AutomaticBackup">
- <a href="{@docRoot}samples/AutoBackupForApps/index.html">Auto Backup for
- Apps</a>
-</h3>
-
-<p>
- Android 6.0 (API level 23) introduces automatic backup for app settings. This
- sample demonstrates how to add filtering rules to an app to manage settings
- backup.
-</p>
-
-<h3 id="Camera2Raw">
- <a href="{@docRoot}samples/Camera2Raw/index.html">Camera 2 Raw</a>
-</h3>
-
-<p>
- This sample demonstrates how to use the
- <a href="{@docRoot}reference/android/hardware/camera2/package-summary.html">
- <code>Camera2</code></a> API to capture RAW camera buffers and save them as
- DNG files.
-</p>
-
-<h3 id="ConfirmCredential">
- <a href="{@docRoot}samples/ConfirmCredential/index.html">Confirm
- Credential</a>
-</h3>
-
-<p>
- This sample demonstrates how to use device credentials as an authentication method in your app.
-</p>
-
-<h3 id="DeviceOwner">
- <a href="{@docRoot}samples/DeviceOwner/index.html">Device Owner</a>
-</h3>
-
-<p>
- This sample demonstrates how to use the device owner features to manage and
- configure a device.
-</p>
-
-<h3 id="DirectShare">
- <a href="{@docRoot}samples/DirectShare/index.html">Direct Share</a>
-</h3>
-
-<p>
- This sample demonstrates how to provide the
- <a href="{@docRoot}about/versions/marshmallow/android-6.0.html#direct-share">Direct
- Share</a> feature. The app shows some options directly in the list of share
- intent candidates.
-</p>
-
-<h3 id="FingerprintDialog">
- <a href="{@docRoot}samples/FingerprintDialog/index.html">Fingerprint
- Dialog</a>
-</h3>
-
-<p>
- This sample demonstrates how to recognize registered fingerprints to
- authenticate your app's user.
-</p>
-
-<h3 id="MidiScope">
- <a href="{@docRoot}samples/MidiScope/index.html">MidiScope</a>
-</h3>
+<!-- TBA
+<img src="sample-img.png" style="float: left; padding-right: 0.5em"
+ width="xxx"/>
+-->
<p>
This sample demonstrates how to use the <a href=
- "{@docRoot}reference/android/media/midi/package-summary.html">MIDI API</a> to
- receive and process MIDI signals coming from an attached input device.
+ "/preview/app-shortcuts.html">app shortcuts API</a> introduced in Android 7.1
+ (API level 25). This API allows an application to define a set of intents
+ which are displayed when a user long-presses on the app's launcher icon.
+ Examples are given for registering links both statically in XML, as well as
+ dynamically at runtime.
</p>
-<h3 id="MidiSynth">
- <a href="{@docRoot}samples/MidiSynth/index.html">MidiSynth</a>
-</h3>
-
<p>
- This sample demonstrates how to use the <a href=
- "{@docRoot}reference/android/media/midi/package-summary.html">MIDI API</a> to
- receive and play MIDI messages coming from an attached input device.
+ <a href="/samples/AppShortcuts/index.html">App shortcuts sample</a>
</p>
-<h3 id="NfcProvisioning">
- <a href="{@docRoot}samples/NfcProvisioning/index.html">NFC Provisioning</a>
-</h3>
+<h3 id="img-kbd-app">Image keyboard app sample</h3>
+
+<!-- TBA
+<img src="sample-img.png" style="float: left; padding-right: 0.5em"
+ width="xxx"/>
+-->
<p>
- This sample demonstrates how to use NFC to provision other devices with a
- specific device owner.
+ This sample demonstrates how to implement the <a href=
+ "/reference/android/view/inputmethod/InputConnection.html#commitContent(android.view.inputmethod.InputContentInfo,%20int,%20android.os.Bundle)">
+ Commit Content API</a>, using the <a href=
+ "/topic/libraries/support-library/index.html">Android Support Library</a>.
+ This API provides a universal way for IMEs to send images and other rich
+ content directly to a text editor in an app, allowing users to compose
+ content using custom emojis, stickers, or other rich content provided by
+ other applications.
</p>
-<h3 id="RuntimePermissions">
- <a href=
- "{@docRoot}samples/RuntimePermissions/index.html">RuntimePermissions</a>
-</h3>
+<p>
+ <a href="/samples/CommitContentSampleApp/index.html">Image keyboard app sample</a>
+</p>
+
+<h3 id="img-kbd-ime">Image keyboard IME sample</h3>
+
+<!-- TBA
+<img src="sample-img.png" style="float: left; padding-right: 0.5em"
+ width="xxx"/>
+-->
<p>
- This sample shows runtime permissions available in Android 6.0 (API level 23)
- and higher. Display the log on screen to follow the execution. If executed on
- an Android 6.0 device, the app displays an additional option to access
- contacts using an 6.0-only optional permission.
+ This sample demonstrates how to write a <a href=
+ "/preview/image-keyboard.html">custom image keyboard</a> using the <a href=
+ "/reference/android/view/inputmethod/InputConnection.html#commitContent(android.view.inputmethod.InputContentInfo,%20int,%20android.os.Bundle)">
+ Commit Content API</a> and the <a href=
+ "/topic/libraries/support-library/index.html">Android Support Library</a>.
+ This keyboard will be displayed inside compatible apps (also using the Commit
+ Content API), allowing users to insert emojis, stickers, or other rich
+ content into text editors.
+</p>
+
+<p>
+ <a href="/samples/CommitContentSampleIME/index.html">Image keyboard IME sample</a>
</p>
diff --git a/docs/html/training/articles/security-tips.jd b/docs/html/training/articles/security-tips.jd
index abf6711..9796d9a 100644
--- a/docs/html/training/articles/security-tips.jd
+++ b/docs/html/training/articles/security-tips.jd
@@ -6,34 +6,32 @@
<div id="tb">
<h2>In this document</h2>
<ol class="nolist">
- <li><a href="#StoringData">Storing Data</a></li>
- <li><a href="#Permissions">Using Permissions</a></li>
- <li><a href="#Networking">Using Networking</a></li>
- <li><a href="#InputValidation">Performing Input Validation</a></li>
- <li><a href="#UserData">Handling User Data</a></li>
+ <li><a href="#StoringData">Storing data</a></li>
+ <li><a href="#Permissions">Using permissions</a></li>
+ <li><a href="#Networking">Using networking</a></li>
+ <li><a href="#InputValidation">Performing input validation</a></li>
+ <li><a href="#UserData">Handling user data</a></li>
<li><a href="#WebView">Using WebView</a></li>
- <li><a href="#Crypto">Using Cryptography</a></li>
- <li><a href="#IPC">Using Interprocess Communication</a></li>
- <li><a href="#DynamicCode">Dynamically Loading Code</a></li>
- <li><a href="#Dalvik">Security in a Virtual Machine</a></li>
- <li><a href="#Native">Security in Native Code</a></li>
+ <li><a href="#Crypto">Using cryptography</a></li>
+ <li><a href="#IPC">Using interprocess communication</a></li>
+ <li><a href="#DynamicCode">Dynamically loading code</a></li>
+ <li><a href="#Dalvik">Security in a virtual machine</a></li>
+ <li><a href="#Native">Security in native code</a></li>
</ol>
<h2>See also</h2>
<ul>
<li><a href="http://source.android.com/tech/security/index.html">Android
-Security Overview</a></li>
+ Security Overview</a></li>
<li><a href="{@docRoot}guide/topics/security/permissions.html">Permissions</a></li>
</ul>
</div></div>
-<p>Android has security features built
-into the operating system that significantly reduce the frequency and impact of
-application security issues. The system is designed so you can typically build your apps with
-default system and file permissions and avoid difficult decisions about security.</p>
+<p>Android has built-in security features that significantly reduce the frequency and impact of
+application security issues. The system is designed so that you can typically build your apps with
+the default system and file permissions and avoid difficult decisions about security.</p>
-<p>Some of the core security features that help you build secure apps
-include:
+<p>The following core security features help you build secure apps:
<ul>
<li>The Android Application Sandbox, which isolates your app data and code execution
from other apps.</li>
@@ -43,47 +41,54 @@
<li>Technologies like ASLR, NX, ProPolice, safe_iop, OpenBSD dlmalloc, OpenBSD
calloc, and Linux mmap_min_addr to mitigate risks associated with common memory
management errors.</li>
-<li>An encrypted filesystem that can be enabled to protect data on lost or
+<li>An encrypted file system that can be enabled to protect data on lost or
stolen devices.</li>
<li>User-granted permissions to restrict access to system features and user data.</li>
<li>Application-defined permissions to control application data on a per-app basis.</li>
</ul>
-<p>Nevertheless, it is important that you be familiar with the Android
+<p>It is important that you be familiar with the Android
security best practices in this document. Following these practices as general coding habits
-will reduce the likelihood of inadvertently introducing security issues that
+ reduces the likelihood of inadvertently introducing security issues that
adversely affect your users.</p>
-<h2 id="StoringData">Storing Data</h2>
+<h2 id="StoringData">Storing data</h2>
<p>The most common security concern for an application on Android is whether the data
that you save on the device is accessible to other apps. There are three fundamental
ways to save data on the device:</p>
+<ul>
+<li>Internal storage.</li>
+<li>External storage.</li>
+<li>Content providers.</li>
+</ul>
+
+The following paragraphs describe the security issues associated with each approach.
+
<h3 id="InternalStorage">Using internal storage</h3>
<p>By default, files that you create on <a
href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal
-storage</a> are accessible only to your app. This
-protection is implemented by Android and is sufficient for most
-applications.</p>
+storage</a> are accessible only to your app.
+ Android implements this protection, and it's sufficient for most applications.</p>
-<p>You should generally avoid using the {@link android.content.Context#MODE_WORLD_WRITEABLE} or
+<p>Generally, avoid the {@link android.content.Context#MODE_WORLD_WRITEABLE} or
{@link android.content.Context#MODE_WORLD_READABLE} modes for
<acronym title="Interprocess Communication">IPC</acronym> files because they do not provide
the ability to limit data access to particular applications, nor do they
-provide any control on data format. If you want to share your data with other
-app processes, you might instead consider using a
+provide any control of data format. If you want to share your data with other
+app processes, instead consider using a
<a href="{@docRoot}guide/topics/providers/content-providers.html">content provider</a>, which
offers read and write permissions to other apps and can make
dynamic permission grants on a case-by-case basis.</p>
-<p>To provide additional protection for sensitive data, you might
-choose to encrypt local files using a key that is not directly accessible to the
-application. For example, a key can be placed in a {@link java.security.KeyStore}
-and protected with a user password that is not stored on the device. While this
+<p>To provide additional protection for sensitive data, you can
+ encrypt local files using a key that is not directly accessible to the
+application. For example, you can place a key in a {@link java.security.KeyStore}
+and protect it with a user password that is not stored on the device. While this
does not protect data from a root compromise that can monitor the user
inputting the password, it can provide protection for a lost device without <a
href="http://source.android.com/tech/encryption/index.html">file system
@@ -94,14 +99,14 @@
<p>Files created on <a
href="{@docRoot}guide/topics/data/data-storage.html#filesExternal">external
-storage</a>, such as SD Cards, are globally readable and writable. Because
+storage</a>, such as SD cards, are globally readable and writable. Because
external storage can be removed by the user and also modified by any
-application, you should not store sensitive information using
+application, don't store sensitive information using
external storage.</p>
-<p>As with data from any untrusted source, you should <a href="#InputValidation">perform input
-validation</a> when handling data from external storage.
-We strongly recommend that you not store executables or
+<p>You should <a href="#InputValidation">Perform input validation</a> when handling
+data from external storage as you would with data from any untrusted source.
+You should not store executables or
class files on external storage prior to dynamic loading. If your app
does retrieve executable files from external storage, the files should be signed and
cryptographically verified prior to dynamic loading.</p>
@@ -117,22 +122,22 @@
href="{@docRoot}guide/topics/manifest/provider-element.html#exported">
android:exported=false</a></code> in the application manifest. Otherwise, set the <code><a
href="{@docRoot}guide/topics/manifest/provider-element.html#exported">android:exported</a></code>
-attribute {@code "true"} to allow other apps to access the stored data.
+attribute to {@code true} to allow other apps to access the stored data.
</p>
<p>When creating a {@link android.content.ContentProvider}
-that will be exported for use by other applications, you can specify a single
+that is exported for use by other applications, you can specify a single
<a href="{@docRoot}guide/topics/manifest/provider-element.html#prmsn">permission
-</a> for reading and writing, or distinct permissions for reading and writing
-within the manifest. We recommend that you limit your permissions to those
+</a> for reading and writing, or you can specify distinct permissions for reading and writing.
+You should limit your permissions to those
required to accomplish the task at hand. Keep in mind that it’s usually
easier to add permissions later to expose new functionality than it is to take
-them away and break existing users.</p>
+them away and impact existing users.</p>
<p>If you are using a content provider
for sharing data between only your own apps, it is preferable to use the
<a href="{@docRoot}guide/topics/manifest/permission-element.html#plevel">{@code
-android:protectionLevel}</a> attribute set to {@code "signature"} protection.
+android:protectionLevel}</a> attribute set to {@code signature} protection.
Signature permissions do not require user confirmation,
so they provide a better user experience and more controlled access to the
content provider data when the apps accessing the data are
@@ -148,7 +153,7 @@
that activates the component. The scope of these permissions can be further
limited by the <code><a
href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html">
-<grant-uri-permission element></a></code>.</p>
+<grant-uri-permission></a></code> element.</p>
<p>When accessing a content provider, use parameterized query methods such as
{@link android.content.ContentProvider#query(Uri,String[],String,String[],String) query()},
@@ -158,11 +163,11 @@
sufficient if the <code>selection</code> argument is built by concatenating user data
prior to submitting it to the method.</p>
-<p>Do not have a false sense of security about the write permission. Consider
-that the write permission allows SQL statements which make it possible for some
+<p>Don't have a false sense of security about the write permission.
+ The write permission allows SQL statements that make it possible for some
data to be confirmed using creative <code>WHERE</code> clauses and parsing the
-results. For example, an attacker might probe for presence of a specific phone
-number in a call-log by modifying a row only if that phone number already
+results. For example, an attacker might probe for the presence of a specific phone
+number in a call log by modifying a row only if that phone number already
exists. If the content provider data has predictable structure, the write
permission may be equivalent to providing both reading and writing.</p>
@@ -172,7 +177,7 @@
-<h2 id="Permissions">Using Permissions</h2>
+<h2 id="Permissions">Using permissions</h2>
<p>Because Android sandboxes applications from each other, applications must explicitly
share resources and data. They do this by declaring the permissions they need for additional
@@ -180,25 +185,25 @@
the camera.</p>
-<h3 id="RequestingPermissions">Requesting Permissions</h3>
+<h3 id="RequestingPermissions">Requesting permissions</h3>
-<p>We recommend minimizing the number of permissions that your app requests.
-Not having access to sensitive permissions reduces the risk of
-inadvertently misusing those permissions, can improve user adoption, and makes
+<p>You should minimize the number of permissions that your app requests.
+Restricting access to sensitive permissions reduces the risk of
+inadvertently misusing those permissions, improves user adoption, and makes
your app less vulnerable for attackers. Generally,
-if a permission is not required for your app to function, do not request it.</p>
+if a permission is not required for your app to function, don't request it.</p>
<p>If it's possible to design your application in a way that does not require
any permissions, that is preferable. For example, rather than requesting access
to device information to create a unique identifier, create a <a
href="{@docRoot}reference/java/util/UUID.html">GUID</a> for your application
-(see the section about <a href="#UserData">Handling User Data</a>). Or, rather than
+(see the section about <a href="#UserData">Handling user data</a>). Or, rather than
using external storage (which requires permission), store data
on the internal storage.</p>
<p>In addition to requesting permissions, your application can use the <a
-href="{@docRoot}guide/topics/manifest/permission-element.html">{@code <permissions>}</a>
-to protect IPC that is security sensitive and will be exposed to other
+href="{@docRoot}guide/topics/manifest/permission-element.html">{@code <permission>}</a>
+ element to protect IPC that is security sensitive and is exposed to other
applications, such as a {@link android.content.ContentProvider}.
In general, we recommend using access controls
other than user confirmed permissions where possible because permissions can
@@ -211,13 +216,14 @@
data over IPC that is available only because your app has permission to access
that data. The clients of your app's IPC interface may not have that same
data-access permission. More details on the frequency and potential effects
-of this issue appear in <a class="external-link"
-href="https://www.usenix.org/legacy/event/sec11/tech/full_papers/Felt.pdf"> this
-research paper</a>, published at USENIX.
+of this issue appear in the research paper <a
+href="https://www.usenix.org/legacy/event/sec11/tech/full_papers/Felt.pdf" class="external-link">
+Permission Re-Delegation: Attacks and Defenses
+</a>, published at USENIX.
-<h3 id="CreatingPermissions">Creating Permissions</h3>
+<h3 id="CreatingPermissions">Creating permissions</h3>
<p>Generally, you should strive to define as few permissions as possible while
satisfying your security requirements. Creating a new permission is relatively
@@ -228,18 +234,18 @@
<p>If you must create a new permission, consider whether you can accomplish
your task with a <a
-href="{@docRoot}guide/topics/manifest/permission-element.html#plevel">"signature"
+href="{@docRoot}guide/topics/manifest/permission-element.html#plevel">signature
protection level</a>. Signature permissions are transparent
-to the user and only allow access by applications signed by the same developer
-as application performing the permission check.</p>
+to the user and allow access only by applications signed by the same developer
+as the application performing the permission check.</p>
<p>If you create a permission with the <a
-href="{@docRoot}guide/topics/manifest/permission-element.html#plevel">"dangerous"
+href="{@docRoot}guide/topics/manifest/permission-element.html#plevel">dangerous
protection level</a>, there are a number of complexities
that you need to consider:
<ul>
<li>The permission must have a string that concisely expresses to a user the
-security decision they will be required to make.</li>
+security decision they are required to make.</li>
<li>The permission string must be localized to many different languages.</li>
<li>Users may choose not to install an application because a permission is
confusing or perceived as risky.</li>
@@ -247,28 +253,28 @@
has not been installed.</li>
</ul>
-<p>Each of these poses a significant non-technical challenge for you as the developer
+<p>Each of these poses a significant nontechnical challenge for you as the developer
while also confusing your users,
-which is why we discourage the use of the "dangerous" permission level.</p>
+which is why we discourages the use of the <em>dangerous</em> permission level.</p>
-<h2 id="Networking">Using Networking</h2>
+<h2 id="Networking">Using networking</h2>
-<p>Network transactions are inherently risky for security, because it involves transmitting
+<p>Network transactions are inherently risky for security, because they involve transmitting
data that is potentially private to the user. People are increasingly aware of the privacy
concerns of a mobile device, especially when the device performs network transactions,
so it's very important that your app implement all best practices toward keeping the user's
data secure at all times.</p>
-<h3 id="IPNetworking">Using IP Networking</h3>
+<h3 id="IPNetworking">Using IP networking</h3>
<p>Networking on Android is not significantly different from other Linux
environments. The key consideration is making sure that appropriate protocols
are used for sensitive data, such as {@link javax.net.ssl.HttpsURLConnection} for
-secure web traffic. We prefer use of HTTPS over HTTP anywhere that HTTPS is
+secure web traffic. You should use HTTPS over HTTP anywhere that HTTPS is
supported on the server, because mobile devices frequently connect on networks
that are not secured, such as public Wi-Fi hotspots.</p>
@@ -278,32 +284,32 @@
wireless networks using Wi-Fi, the use of secure networking is strongly
encouraged for all applications that communicate over the network.</p>
-<p>We have seen some applications use <a
-href="http://en.wikipedia.org/wiki/Localhost">localhost</a> network ports for
-handling sensitive IPC. We discourage this approach since these interfaces are
-accessible by other applications on the device. Instead, you should use an Android IPC
-mechanism where authentication is possible such as with a {@link android.app.Service}. (Even
-worse than using loopback is to bind to INADDR_ANY since then your application
-may receive requests from anywhere.)</p>
+<p>Some applications use <a
+href="http://en.wikipedia.org/wiki/Localhost" class="external-link">localhost</a> network ports for
+handling sensitive IPC. You should not use this approach because these interfaces are
+accessible by other applications on the device. Instead, use an Android IPC
+mechanism where authentication is possible, such as with a {@link android.app.Service}.
+Binding to INADDR_ANY is worse than using loopback because then your application
+may receive requests from anywhere.</p>
-<p>Also, one common issue that warrants repeating is to make sure that you do
-not trust data downloaded from HTTP or other insecure protocols. This includes
+<p>Make sure that you don't
+ trust data downloaded from HTTP or other insecure protocols. This includes
validation of input in {@link android.webkit.WebView} and
any responses to intents issued against HTTP.</p>
-<h3>Using Telephony Networking</h3>
+<h3>Using telephony networking</h3>
<p>The <acronym title="Short Message Service">SMS</acronym> protocol was primarily designed for
user-to-user communication and is not well-suited for apps that want to transfer data.
-Due to the limitations of SMS, we strongly recommend the use of <a
+Due to the limitations of SMS, you should use <a
href="{@docRoot}google/gcm/index.html">Google Cloud Messaging</a> (GCM)
and IP networking for sending data messages from a web server to your app on a user device.</p>
<p>Beware that SMS is neither encrypted nor strongly
-authenticated on either the network or the device. In particular, any SMS receiver
-should expect that a malicious user may have sent the SMS to your application—Do
-not rely on unauthenticated SMS data to perform sensitive commands.
+authenticated on either the network or the device. In particular, any SMS receiver
+should expect that a malicious user may have sent the SMS to your application. Don't
+ rely on unauthenticated SMS data to perform sensitive commands.
Also, you should be aware that SMS may be subject to spoofing and/or
interception on the network. On the Android-powered device itself, SMS
messages are transmitted as broadcast intents, so they may be read or captured
@@ -314,32 +320,32 @@
-<h2 id="InputValidation">Performing Input Validation</h2>
+<h2 id="InputValidation">Performing input validation</h2>
<p>Insufficient input validation is one of the most common security problems
-affecting applications, regardless of what platform they run on. Android does
-have platform-level countermeasures that reduce the exposure of applications to
-input validation issues and you should use those features where possible. Also
-note that selection of type-safe languages tends to reduce the likelihood of
+affecting applications, regardless of what platform they run on. Android
+has platform-level countermeasures that reduce the exposure of applications to
+input validation issues, and you should use those features where possible. Also
+note that the selection of type-safe languages tends to reduce the likelihood of
input validation issues.</p>
-<p>If you are using native code, then any data read from files, received over
+<p>If you are using native code, any data read from files, received over
the network, or received from an IPC has the potential to introduce a security
issue. The most common problems are <a
-href="http://en.wikipedia.org/wiki/Buffer_overflow">buffer overflows</a>, <a
-href="http://en.wikipedia.org/wiki/Double_free#Use_after_free">use after
+href="http://en.wikipedia.org/wiki/Buffer_overflow" class="external-link">buffer overflows</a>, <a
+href="http://en.wikipedia.org/wiki/Double_free#Use_after_free" class="external-link">use after
free</a>, and <a
-href="http://en.wikipedia.org/wiki/Off-by-one_error">off-by-one errors</a>.
+href="http://en.wikipedia.org/wiki/Off-by-one_error" class="external-link">off-by-one errors</a>.
Android provides a number of technologies like <acronym
title="Address Space Layout Randomization">ASLR</acronym> and <acronym
title="Data Execution Prevention">DEP</acronym> that reduce the
-exploitability of these errors, but they do not solve the underlying problem.
-You can prevent these vulneratbilities by careful handling pointers and managing
+exploitability of these errors, but they don't solve the underlying problem.
+You can prevent these vulnerabilities by carefully handling pointers and managing
buffers.</p>
-<p>Dynamic, string based languages such as JavaScript and SQL are also subject
+<p>Dynamic, string-based languages such as JavaScript and SQL are also subject
to input validation problems due to escape characters and <a
-href="http://en.wikipedia.org/wiki/Code_injection">script injection</a>.</p>
+href="http://en.wikipedia.org/wiki/Code_injection" class="external-link">script injection</a>.</p>
<p>If you are using data within queries that are submitted to an SQL database or a
content provider, SQL injection may be an issue. The best defense is to use
@@ -348,60 +354,59 @@
Limiting permissions to read-only or write-only can also reduce the potential
for harm related to SQL injection.</p>
-<p>If you cannot use the security features above, we strongly recommend the use
-of well-structured data formats and verifying that the data conforms to the
+<p>If you can't use the security features above, you should make sure to use
+well-structured data formats and verify that the data conforms to the
expected format. While blacklisting of characters or character-replacement can
-be an effective strategy, these techniques are error-prone in practice and
+be an effective strategy, these techniques are error prone in practice and
should be avoided when possible.</p>
-<h2 id="UserData">Handling User Data</h2>
+<h2 id="UserData">Handling user data</h2>
<p>In general, the best approach for user data security is to minimize the use of APIs that access
sensitive or personal user data. If you have access to user data and can avoid
-storing or transmitting the information, do not store or transmit the data.
-Finally, consider if there is a way that your application logic can be
+storing or transmitting it, don't store or transmit the data.
+Consider if there is a way that your application logic can be
implemented using a hash or non-reversible form of the data. For example, your
-application might use the hash of an an email address as a primary key, to
+application might use the hash of an email address as a primary key to
avoid transmitting or storing the email address. This reduces the chances of
inadvertently exposing data, and it also reduces the chance of attackers
attempting to exploit your application.</p>
<p>If your application accesses personal information such as passwords or
-usernames, keep in mind that some jurisdictions may require you to provide a
-privacy policy explaining your use and storage of that data. So following the
+user names, keep in mind that some jurisdictions may require you to provide a
+privacy policy explaining your use and storage of that data. Following the
security best practice of minimizing access to user data may also simplify
compliance.</p>
<p>You should also consider whether your application might be inadvertently
exposing personal information to other parties such as third-party components
for advertising or third-party services used by your application. If you don't
-know why a component or service requires a personal information, don’t
+know why a component or service requires personal information, don’t
provide it. In general, reducing the access to personal information by your
-application will reduce the potential for problems in this area.</p>
+application reduces the potential for problems in this area.</p>
-<p>If access to sensitive data is required, evaluate whether that information
-must be transmitted to a server, or whether the operation can be performed on
-the client. Consider running any code using sensitive data on the client to
-avoid transmitting user data.</p>
-
-<p>Also, make sure that you do not inadvertently expose user data to other
-application on the device through overly permissive IPC, world writable files,
-or network sockets. This is a special case of leaking permission-protected data,
+<p>If your app requires access to sensitive data, evaluate whether you need to
+ transmit it to a server or you can run the operation on
+the client. Consider running any code using sensitive data on the client to
+avoid transmitting user data. Also, make sure that you do not inadvertently expose user
+ data to other
+applications on the device through overly permissive IPC, world-writable files,
+or network sockets. Overly permissive IPC is a special case of leaking permission-protected data,
discussed in the <a href="#RequestingPermissions">Requesting Permissions</a> section.</p>
<p>If a <acronym title="Globally Unique Identifier">GUID</acronym>
-is required, create a large, unique number and store it. Do not
-use phone identifiers such as the phone number or IMEI which may be associated
+is required, create a large, unique number and store it. Don't
+use phone identifiers such as the phone number or IMEI, which may be associated
with personal information. This topic is discussed in more detail in the <a
-href="http://android-developers.blogspot.com/2011/03/identifying-app-installations.html">Android
-Developer Blog</a>.</p>
+href="http://android-developers.blogspot.com/2011/03/identifying-app-installations.html"
+>Android Developer Blog</a>.</p>
<p>Be careful when writing to on-device logs.
-In Android, logs are a shared resource, and are available
+In Android, logs are a shared resource and are available
to an application with the {@link android.Manifest.permission#READ_LOGS} permission.
Even though the phone log data
is temporary and erased on reboot, inappropriate logging of user information
@@ -414,19 +419,23 @@
<h2 id="WebView">Using WebView</h2>
-<p>Because {@link android.webkit.WebView} consumes web content that can include HTML and JavaScript,
+<p>Because {@link android.webkit.WebView} consumes web content that can include HTML
+ and JavaScript,
improper use can introduce common web security issues such as <a
-href="http://en.wikipedia.org/wiki/Cross_site_scripting">cross-site-scripting</a>
+href="http://en.wikipedia.org/wiki/Cross_site_scripting" class="external-link">
+cross-site-scripting</a>
(JavaScript injection). Android includes a number of mechanisms to reduce
-the scope of these potential issues by limiting the capability of {@link android.webkit.WebView} to
+the scope of these potential issues by limiting the capability of
+ {@link android.webkit.WebView} to
the minimum functionality required by your application.</p>
-<p>If your application does not directly use JavaScript within a {@link android.webkit.WebView}, do
-<em>not</em> call {@link android.webkit.WebSettings#setJavaScriptEnabled setJavaScriptEnabled()}.
+<p>If your application doesn't directly use JavaScript within a {@link android.webkit.WebView},
+ <em>do not</em> call
+ {@link android.webkit.WebSettings#setJavaScriptEnabled setJavaScriptEnabled()}.
Some sample code uses this method, which you might repurpose in production
application, so remove that method call if it's not required. By default,
{@link android.webkit.WebView} does
-not execute JavaScript so cross-site-scripting is not possible.</p>
+not execute JavaScript, so cross-site-scripting is not possible.</p>
<p>Use {@link android.webkit.WebView#addJavascriptInterface
addJavaScriptInterface()} with
@@ -441,55 +450,55 @@
<p>If your application accesses sensitive data with a
{@link android.webkit.WebView}, you may want to use the
{@link android.webkit.WebView#clearCache clearCache()} method to delete any files stored
-locally. Server-side
-headers like <code>no-cache</code> can also be used to indicate that an application should
+locally. You can also use server-side
+headers such as <code>no-cache</code> to indicate that an application should
not cache particular content.</p>
<p>Devices running platforms older than Android 4.4 (API level 19)
use a version of {@link android.webkit webkit} that has a number of security issues.
As a workaround, if your app is running on these devices, it
-should confirm that {@link android.webkit.WebView} objects display only trusted
-content. You should also use the updatable security {@link
-java.security.Provider Provider} object to make sure your app isn’t exposed to
-potential vulnerabilities in SSL, as described in <a
+must confirm that {@link android.webkit.WebView} objects display only trusted
+content. To make sure your app isn’t exposed to
+potential vulnerabilities in SSL, use the updatable security {@link
+java.security.Provider Provider} object as described in <a
href="{@docRoot}training/articles/security-gms-provider.html">Updating Your
Security Provider to Protect Against SSL Exploits</a>. If your application must
render content from the open web, consider providing your own renderer so
you can keep it up to date with the latest security patches.</p>
-<h3 id="Credentials">Handling Credentials</h3>
+<h3 id="Credentials">Handling credentials</h3>
-<p>In general, we recommend minimizing the frequency of asking for user
-credentials—to make phishing attacks more conspicuous, and less likely to be
-successful. Instead use an authorization token and refresh it.</p>
+<p>To make phishing attacks more conspicuous and less likely to be
+successful, minimize the frequency of asking for user
+credentials. Instead use an authorization token and refresh it.</p>
-<p>Where possible, username and password should not be stored on the device.
-Instead, perform initial authentication using the username and password
-supplied by the user, and then use a short-lived, service-specific
+<p>Where possible, don't store user names and passwords on the device.
+Instead, perform initial authentication using the user name and password
+ supplied by the user, and then use a short-lived, service-specific
authorization token.</p>
-<p>Services that will be accessible to multiple applications should be accessed
+<p>Services that are accessible to multiple applications should be accessed
using {@link android.accounts.AccountManager}. If possible, use the
-{@link android.accounts.AccountManager} class to invoke a cloud-based service and do not store
+{@link android.accounts.AccountManager} class to invoke a cloud-based service and don't store
passwords on the device.</p>
<p>After using {@link android.accounts.AccountManager} to retrieve an
-{@link android.accounts.Account}, {@link android.accounts.Account#CREATOR}
-before passing in any credentials, so that you do not inadvertently pass
+{@link android.accounts.Account}, use {@link android.accounts.Account#CREATOR}
+before passing in any credentials so that you do not inadvertently pass
credentials to the wrong application.</p>
-<p>If credentials are to be used only by applications that you create, then you
-can verify the application which accesses the {@link android.accounts.AccountManager} using
+<p>If credentials are used only by applications that you create, you
+can verify the application that accesses the {@link android.accounts.AccountManager} using
{@link android.content.pm.PackageManager#checkSignatures checkSignature()}.
-Alternatively, if only one application will use the credential, you might use a
+Alternatively, if only one application uses the credential, you might use a
{@link java.security.KeyStore} for storage.</p>
-<h2 id="Crypto">Using Cryptography</h2>
+<h2 id="Crypto">Using cryptography</h2>
<p>In addition to providing data isolation, supporting full-filesystem
encryption, and providing secure communications channels, Android provides a
@@ -500,21 +509,21 @@
retrieve a file from a known location, a simple HTTPS URI may be adequate and
requires no knowledge of cryptography. If you need a secure
tunnel, consider using {@link javax.net.ssl.HttpsURLConnection} or
-{@link javax.net.ssl.SSLSocket}, rather than writing your own protocol.</p>
+{@link javax.net.ssl.SSLSocket} rather than writing your own protocol.</p>
-<p>If you do find yourself needing to implement your own protocol, we strongly
-recommend that you <em>not</em> implement your own cryptographic algorithms. Use
+<p>If you do need to implement your own protocol, you should <em>not</em>
+implement your own cryptographic algorithms. Use
existing cryptographic algorithms such as those in the implementation of AES or
RSA provided in the {@link javax.crypto.Cipher} class.</p>
<p>Use a secure random number generator, {@link java.security.SecureRandom},
-to initialize any cryptographic keys, {@link javax.crypto.KeyGenerator}.
+to initialize any cryptographic keys generated by {@link javax.crypto.KeyGenerator}.
Use of a key that is not generated with a secure random
-number generator significantly weakens the strength of the algorithm, and may
+number generator significantly weakens the strength of the algorithm and may
allow offline attacks.</p>
-<p>If you need to store a key for repeated use, use a mechanism like
- {@link java.security.KeyStore} that
+<p>If you need to store a key for repeated use, use a mechanism, such as
+ {@link java.security.KeyStore}, that
provides a mechanism for long term storage and retrieval of cryptographic
keys.</p>
@@ -522,10 +531,10 @@
-<h2 id="IPC">Using Interprocess Communication</h2>
+<h2 id="IPC">Using interprocess communication</h2>
<p>Some apps attempt to implement IPC using traditional Linux
-techniques such as network sockets and shared files. We strongly encourage you to instead
+techniques such as network sockets and shared files. However, you should instead
use Android system functionality for IPC such as {@link android.content.Intent},
{@link android.os.Binder} or {@link android.os.Messenger} with a {@link
android.app.Service}, and {@link android.content.BroadcastReceiver}.
@@ -535,19 +544,19 @@
<p>Many of the security elements are shared across IPC mechanisms.
If your IPC mechanism is not intended for use by other applications, set the
-{@code android:exported} attribute to {@code "false"} in the component's manifest element,
+{@code android:exported} attribute to {@code false} in the component's manifest element,
such as for the <a
-href="{@docRoot}guide/topics/manifest/service-element.html#exported">{@code <service>}</a>
+href="{@docRoot}guide/topics/manifest/service-element.html#exported">{@code <service>}</a>
element. This is useful for applications that consist of multiple processes
-within the same UID, or if you decide late in development that you do not
-actually want to expose functionality as IPC but you don’t want to rewrite
+within the same UID or if you decide late in development that you don't
+actually want to expose functionality as IPC, but you don’t want to rewrite
the code.</p>
-<p>If your IPC is intended to be accessible to other applications, you can
+<p>If your IPC is accessible to other applications, you can
apply a security policy by using the <a
-href="{@docRoot}guide/topics/manifest/permission-element.html">{@code <permission>}</a>
+href="{@docRoot}guide/topics/manifest/permission-element.html">{@code <permission>}</a>
element. If IPC is between your own separate apps that are signed with the same key,
-it is preferable to use {@code "signature"} level permission in the <a
+it is preferable to use {@code signature} level permission in the <a
href="{@docRoot}guide/topics/manifest/permission-element.html#plevel">{@code
android:protectionLevel}</a>.</p>
@@ -556,31 +565,42 @@
<h3>Using intents</h3>
-<p>Intents are the preferred mechanism for asynchronous IPC in Android.
+<p>For activities and broadcast receivers, intents are the preferred mechanism for
+ asynchronous IPC in Android.
Depending on your application requirements, you might use {@link
android.content.Context#sendBroadcast sendBroadcast()}, {@link
android.content.Context#sendOrderedBroadcast sendOrderedBroadcast()},
or an explicit intent to a specific application component.</p>
-<p>Note that ordered broadcasts can be “consumed” by a recipient, so they
-may not be delivered to all applications. If you are sending an intent that must be delivered
-to a specific receiver, then you must use an explicit intent that declares the receiver
-by nameintent.</p>
+<p class="caution"><strong>Caution:</strong> If you use an intent to bind to a
+ {@link android.app.Service}, ensure that your app is secure by using an
+ <a href="{@docRoot}guide/components/intents-filters.html#Types">explicit</a>
+intent. Using an implicit intent to start a service is a
+security hazard because you can't be certain what service will respond to the intent,
+and the user can't see which service starts. Beginning with Android 5.0 (API level 21),
+ the system
+throws an exception if you call {@link android.content.Context#bindService bindService()}
+with an implicit intent.</p>
-<p>Senders of an intent can verify that the recipient has a permission
-specifying a non-Null permission with the method call. Only applications with that
-permission will receive the intent. If data within a broadcast intent may be
+<p>Note that ordered broadcasts can be <em>consumed</em> by a recipient, so they
+may not be delivered to all applications. If you are sending an intent that must be delivered
+to a specific receiver, you must use an explicit intent that declares the receiver
+by name.</p>
+
+<p>Senders of an intent can verify that the recipient has permission
+ by specifying a non-null permission with the method call. Only applications with that
+permission receive the intent. If data within a broadcast intent may be
sensitive, you should consider applying a permission to make sure that
-malicious applications cannot register to receive those messages without
-appropriate permissions. In those circumstances, you may also consider
+malicious applications can't register to receive those messages without
+appropriate permissions. In those circumstances, you may also consider
invoking the receiver directly, rather than raising a broadcast.</p>
<p class="note"><strong>Note:</strong> Intent filters should not be considered
-a security feature—components
+a security feature. Components
can be invoked with explicit intents and may not have data that would conform to the intent
-filter. You should perform input validation within your intent receiver to
+filter. To
confirm that it is properly formatted for the invoked receiver, service, or
-activity.</p>
+activity, perform input validation within your intent receiver.</p>
@@ -589,26 +609,32 @@
<p>A {@link android.app.Service} is often used to supply functionality for other applications to
use. Each service class must have a corresponding <a
-href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a> declaration in its
+href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a>
+ declaration in its
manifest file.</p>
<p>By default, services are not exported and cannot be invoked by any other
-application. However, if you add any intent filters to the service declaration, then it is exported
+application. However, if you add any intent filters to the service declaration, it is exported
by default. It's best if you explicitly declare the <a
href="{@docRoot}guide/topics/manifest/service-element.html#exported">{@code
android:exported}</a> attribute to be sure it behaves as you'd like.
Services can also be protected using the <a
href="{@docRoot}guide/topics/manifest/service-element.html#prmsn">{@code android:permission}</a>
-attribute. By doing so, other applications will need to declare
+attribute. By doing so, other applications need to declare
a corresponding <code><a
href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a>
</code> element in their own manifest to be
able to start, stop, or bind to the service.</p>
+<p class="note"><strong>Note:</strong> If your app targets Android 5.0 (API level 21) or later,
+ you should use the {@link android.app.job.JobScheduler} to execute background
+ services. For more information about {@link android.app.job.JobScheduler}, see its
+ {@link android.app.job.JobScheduler API-reference documentation}.</p>
+
<p>A service can protect individual IPC calls into it with permissions, by
calling {@link android.content.Context#checkCallingPermission
checkCallingPermission()} before executing
-the implementation of that call. We generally recommend using the
+the implementation of that call. You should use the
declarative permissions in the manifest, since those are less prone to
oversight.</p>
@@ -620,24 +646,24 @@
preferred mechanism for RPC-style IPC in Android. They provide a well-defined
interface that enables mutual authentication of the endpoints, if required.</p>
-<p>We strongly encourage designing interfaces in a manner that does not require
-interface specific permission checks. {@link android.os.Binder} and
+<p>You should design your app interfaces in a manner that does not require
+interface-specific permission checks. {@link android.os.Binder} and
{@link android.os.Messenger} objects are not declared within the
application manifest, and therefore you cannot apply declarative permissions
directly to them. They generally inherit permissions declared in the
application manifest for the {@link android.app.Service} or {@link
android.app.Activity} within which they are
implemented. If you are creating an interface that requires authentication
-and/or access controls, those controls must be
-explicitly added as code in the {@link android.os.Binder} or {@link android.os.Messenger}
+and/or access controls, you must explicitly add those controls
+ as code in the {@link android.os.Binder} or {@link android.os.Messenger}
interface.</p>
-<p>If providing an interface that does require access controls, use {@link
+<p>If you are providing an interface that does require access controls, use {@link
android.content.Context#checkCallingPermission checkCallingPermission()}
to verify whether the
caller has a required permission. This is especially important
before accessing a service on behalf of the caller, as the identify of your
-application is passed to other interfaces. If invoking an interface provided
+application is passed to other interfaces. If you are invoking an interface provided
by a {@link android.app.Service}, the {@link
android.content.Context#bindService bindService()}
invocation may fail if you do not have permission to access the given service.
@@ -660,8 +686,8 @@
is intended for use by other applications, you
may want to apply security permissions to receivers using the <code><a
href="{@docRoot}guide/topics/manifest/receiver-element.html">
-<receiver></a></code> element within the application manifest. This will
-prevent applications without appropriate permissions from sending an intent to
+<receiver></a></code> element within the application manifest. This
+prevents applications without appropriate permissions from sending an intent to
the {@link android.content.BroadcastReceiver}.</p>
@@ -671,57 +697,58 @@
-<h2 id="DynamicCode">Dynamically Loading Code</h2>
+<h2 id="DynamicCode">Dynamically loading code</h2>
<p>We strongly discourage loading code from outside of your application APK.
Doing so significantly increases the likelihood of application compromise due
-to code injection or code tampering. It also adds complexity around version
-management and application testing. Finally, it can make it impossible to
+to code injection or code tampering. It also adds complexity around version
+management and application testing. It can also make it impossible to
verify the behavior of an application, so it may be prohibited in some
environments.</p>
<p>If your application does dynamically load code, the most important thing to
-keep in mind about dynamically loaded code is that it runs with the same
-security permissions as the application APK. The user made a decision to
-install your application based on your identity, and they are expecting that
+keep in mind about dynamically-loaded code is that it runs with the same
+security permissions as the application APK. The user makes a decision to
+install your application based on your identity, and the user expects that
you provide any code run within the application, including code that is
dynamically loaded.</p>
<p>The major security risk associated with dynamically loading code is that the
code needs to come from a verifiable source. If the modules are included
-directly within your APK, then they cannot be modified by other applications.
+directly within your APK, they cannot be modified by other applications.
This is true whether the code is a native library or a class being loaded using
-{@link dalvik.system.DexClassLoader}. We have seen many instances of applications
-attempting to load code from insecure locations, such as downloaded from the
-network over unencrypted protocols or from world writable locations such as
+{@link dalvik.system.DexClassLoader}. Many applications
+attempt to load code from insecure locations, such as downloaded from the
+network over unencrypted protocols or from world-writable locations such as
external storage. These locations could allow someone on the network to modify
-the content in transit, or another application on a users device to modify the
-content on the device, respectively.</p>
+the content in transit or another application on a user's device to modify the
+content on the device.</p>
-<h2 id="Dalvik">Security in a Virtual Machine</h2>
+<h2 id="Dalvik">Security in a virtual machine</h2>
<p>Dalvik is Android's runtime virtual machine (VM). Dalvik was built specifically for Android,
but many of the concerns regarding secure code in other virtual machines also apply to Android.
In general, you shouldn't concern yourself with security issues relating to the virtual machine.
-Your application runs in a secure sandbox environment, so other processes on the system cannnot
+Your application runs in a secure sandbox environment, so other processes on the system can't
access your code or private data.</p>
-<p>If you're interested in diving deeper on the subject of virtual machine security,
-we recommend that you familiarize yourself with some
+<p>If you're interested in learning more about virtual machine security,
+ familiarize yourself with some
existing literature on the subject. Two of the more popular resources are:
<ul>
-<li><a href="http://www.securingjava.com/toc.html">
-http://www.securingjava.com/toc.html</a></li>
+<li><a href="http://www.securingjava.com/toc.html" class="external-link">
+Securing Java</a></li>
<li><a
-href="https://www.owasp.org/index.php/Java_Security_Resources">
-https://www.owasp.org/index.php/Java_Security_Resources</a></li>
+href="https://www.owasp.org/index.php/Category:Java#tab=Related_3rd_Party_Projects"
+ class="external-link">
+Related 3rd party Projects</a></li>
</ul></p>
-<p>This document is focused on the areas which are Android specific or
+<p>This document focuses on areas that are Android specific or
different from other VM environments. For developers experienced with VM
programming in other environments, there are two broad issues that may be
different about writing apps for Android:
@@ -742,21 +769,19 @@
-<h2 id="Native">Security in Native Code</h2>
+<h2 id="Native">Security in native code</h2>
-<p>In general, we encourage developers to use the Android SDK for
+<p>In general, you should use the Android SDK for
application development, rather than using native code with the
<a href="{@docRoot}tools/sdk/ndk/index.html">Android NDK</a>. Applications built
with native code are more complex, less portable, and more like to include
-common memory corruption errors such as buffer overflows.</p>
+common memory-corruption errors such as buffer overflows.</p>
-<p>Android is built using the Linux kernel and being familiar with Linux
-development security best practices is especially useful if you are going to
-use native code. Linux security practices are beyond the scope of this document,
-but one of the most popular resources is “Secure Programming for
-Linux and Unix HOWTO”, available at <a
-href="http://www.dwheeler.com/secure-programs">
-http://www.dwheeler.com/secure-programs</a>.</p>
+<p>Android is built using the Linux kernel, and being familiar with Linux
+development security best practices is especially useful if you are
+using native code. Linux security practices are beyond the scope of this document,
+but one of the most popular resources is <a href="http://www.dwheeler.com/secure-programs"
+ class="external-link">Secure Programming HOWTO - Creating Secure Software</a>.</p>
<p>An important difference between Android and most Linux environments is the
Application Sandbox. On Android, all applications run in the Application
@@ -765,6 +790,5 @@
every application is given a unique <acronym title="User Identifier">UID</acronym>
with very limited permissions. This is discussed in more detail in the <a
href="http://source.android.com/tech/security/index.html">Android Security
-Overview</a> and you should be familiar with application permissions even if
+Overview</a>, and you should be familiar with application permissions even if
you are using native code.</p>
-
diff --git a/docs/html/training/location/display-address.jd b/docs/html/training/location/display-address.jd
index daa6fd3..088e926 100644
--- a/docs/html/training/location/display-address.jd
+++ b/docs/html/training/location/display-address.jd
@@ -7,11 +7,11 @@
<h2>This lesson teaches you how to</h2>
<ol>
- <li><a href="#connect">Get a Geographic Location</a></li>
- <li><a href="#fetch-address">Define an Intent Service to Fetch the
- Address</a></li>
- <li><a href="#start-intent">Start the Intent Service</a></li>
- <li><a href="#result-receiver">Receive the Geocoding Results</a></li>
+ <li><a href="#connect">Get a geographic location</a></li>
+ <li><a href="#fetch-address">Define an intent service to fetch the
+ address</a></li>
+ <li><a href="#start-intent">Start the intent service</a></li>
+ <li><a href="#result-receiver">Receive the geocoding results</a></li>
</ol>
<h2>You should also read</h2>
@@ -58,7 +58,7 @@
convert a geographic location to an address. The method returns an estimated
street address corresponding to a given latitude and longitude.</p>
-<h2 id="connect">Get a Geographic Location</h2>
+<h2 id="connect">Get a geographic location</h2>
<p>The last known location of the device is a useful starting point for the
address lookup feature. The lesson on
@@ -69,12 +69,12 @@
<a href="{@docRoot}reference/com/google/android/gms/location/FusedLocationProviderApi.html">fused
location provider</a> to find the latest location of the device.</p>
-<p>To access the fused location provider, you need to create an instance of the
+<p>To access the fused location provider, create an instance of the
Google Play services API client. To learn how to connect your client, see
<a href="{@docRoot}training/location/retrieve-current.html#play-services">Connect
to Google Play Services</a>.</p>
-<p>In order for the fused location provider to retrieve a precise street
+<p>To enable the fused location provider to retrieve a precise street
address, set the location permission in your app manifest to
{@code ACCESS_FINE_LOCATION}, as shown in the following example:</p>
@@ -86,12 +86,12 @@
</manifest>
</pre>
-<h2 id="fetch-address">Define an Intent Service to Fetch the Address</h2>
+<h2 id="fetch-address">Define an intent service to fetch the address</h2>
<p>The {@link android.location.Geocoder#getFromLocation getFromLocation()}
method provided by the {@link android.location.Geocoder} class accepts a
- latitude and longitude, and returns a list of addresses. The method is
- synchronous, and may take a long time to do its work, so you should not call
+ latitude and longitude and returns a list of addresses. The method is
+ synchronous and may take a long time to do its work, so you should not call
it from the main, user interface (UI) thread of your app.</p>
<p>The {@link android.app.IntentService IntentService} class provides a
@@ -100,23 +100,23 @@
Note that the {@link android.os.AsyncTask AsyncTask} class also allows you to
perform background operations, but it's designed for short operations. An
{@link android.os.AsyncTask AsyncTask} shouldn't keep a reference to the UI if
- the activity is recreated, for example when the device is rotated. In
+ the activity is re-created, such as when the device is rotated. In
contrast, an {@link android.app.IntentService IntentService} doesn't need to
be cancelled when the activity is rebuilt.</p>
<p>Define a {@code FetchAddressIntentService} class that extends
{@link android.app.IntentService}. This class is your address lookup service.
- The intent service handles an intent asynchronously on a worker thread, and
+ The intent service handles an intent asynchronously on a worker thread and
stops itself when it runs out of work. The intent extras provide the data
needed by the service, including a {@link android.location.Location} object
- for conversion to an address, and a {@link android.os.ResultReceiver} object
+ for conversion to an address and a {@link android.os.ResultReceiver} object
to handle the results of the address lookup. The service uses a {@link
- android.location.Geocoder} to fetch the address for the location, and sends
+ android.location.Geocoder} to fetch the address for the location and sends
the results to the {@link android.os.ResultReceiver}.</p>
-<h3>Define the Intent Service in your App Manifest</h3>
+<h3>Define the intent service in your app manifest</h3>
-<p>Add an entry to your app manifest defining the intent service:</p>
+<p>Add an entry to your app manifest that defines the intent service, as shown here:</p>
<pre>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
@@ -131,26 +131,26 @@
</manifest>
</pre>
-<p class="note"><strong>Note:</strong> The {@code <service>} element in
- the manifest doesn't need to include an intent filter, because your main
+<p class="note"><strong>Note:</strong> The {@code <service>} element in
+ the manifest doesn't need to include an intent filter because your main
activity creates an explicit intent by specifying the name of the class to use
for the intent.</p>
-<h3>Create a Geocoder</h3>
+<h3>Create a geocoder</h3>
<p>The process of converting a geographic location to an address is called
- <em>reverse geocoding</em>. To perform the main work of the intent service,
- that is, your reverse geocoding request, implement
+ <em>reverse geocoding</em>. To perform the main work of the intent service (your reverse
+ geocoding request), implement
{@link android.app.IntentService#onHandleIntent onHandleIntent()} within the
{@code FetchAddressIntentService} class. Create a
{@link android.location.Geocoder} object to handle the reverse geocoding.</p>
<p>A locale represents a specific geographical or linguistic region. Locale
- objects are used to adjust the presentation of information, such as numbers or
- dates, to suit the conventions in the region represented by the locale. Pass a
+ objects adjust the presentation of information, such as numbers or
+ dates, to suit the conventions in the region that is represented by the locale. Pass a
<a href="{@docRoot}reference/java/util/Locale.html">{@code Locale}</a> object
- to the {@link android.location.Geocoder} object, to ensure that the resulting
- address is localized to the user's geographic region.</p>
+ to the {@link android.location.Geocoder} object to ensure that the resulting
+ address is localized to the user's geographic region. Here is an example:</p>
<pre>
@Override
@@ -162,7 +162,7 @@
<h3 id="retrieve-street-address">Retrieve the street address data</h3>
-<p>The next step is to retrieve the street address from the geocoder, handle
+<p>You can now retrieve the street address from the geocoder, handle
any errors that may occur, and send the results back to the activity that
requested the address. To report the results of the geocoding
process, you need two numeric constants that indicate success or failure.
@@ -185,32 +185,34 @@
<p>To get a street address corresponding to a geographical location, call
{@link android.location.Geocoder#getFromLocation getFromLocation()},
- passing it the latitude and longitude from the location object, and the
- maximum number of addresses you want returned. In this case, you want just one
- address. The geocoder returns an array of addresses. If no addresses were
+ passing it the latitude and longitude from the location object and the
+ maximum number of addresses that you want returned. In this case, you want just one
+ address. The geocoder returns an array of addresses. If no addresses are
found to match the given location, it returns an empty list. If there is no
backend geocoding service available, the geocoder returns null.</p>
-<p>Check for the following errors as shown in the code sample below. If an error
- occurs, place the corresponding error message in the {@code errorMessage}
- variable, so you can send it back to the requesting activity:</p>
+<p>Check for the following errors, as shown in the code sample below:</p>
<ul>
- <li><strong>No location data provided</strong> - The intent extras do not
- include the {@link android.location.Location} object required for reverse
+ <li><strong>No location data provided</strong> – The intent extras do not
+ include the {@link android.location.Location} object that is required for reverse
geocoding.</li>
- <li><strong>Invalid latitude or longitude used</strong> - The latitude
- and/or longitude values provided in the {@link android.location.Location}
+ <li><strong>Invalid latitude or longitude used</strong> – The latitude
+ and/or longitude values that are provided in the {@link android.location.Location}
object are invalid.</li>
- <li><strong>No geocoder available</strong> - The background geocoding service
- is not available, due to a network error or IO exception.</li>
- <li><strong>Sorry, no address found</strong> - The geocoder could not find an
+ <li><strong>No geocoder available</strong> – The background geocoding service
+ is not available due to a network error or IO exception.</li>
+ <li><strong>Sorry, no address found</strong> – The geocoder can't find an
address for the given latitude/longitude.</li>
</ul>
+<p>If an error
+ occurs, place the corresponding error message in the {@code errorMessage}
+ variable so that you can send it back to the requesting activity.
+
<p>To get the individual lines of an address object, use the
{@link android.location.Address#getAddressLine getAddressLine()}
- method provided by the {@link android.location.Address} class. Then join the
+ method that is provided by the {@link android.location.Address} class. Join the
lines into a list of address fragments ready to return to the activity that
requested the address.</p>
@@ -220,7 +222,7 @@
results consist of the previously-mentioned numeric success/failure code and
a string. In the case of a successful reverse geocoding, the string contains
the address. In the case of a failure, the string contains the error message,
- as shown in the code sample below:</p>
+ as shown in this code sample:</p>
<pre>
@Override
@@ -280,18 +282,18 @@
<h3 id="return-address">Return the address to the requestor</h3>
-<p>The final thing the intent service must do is send the address back to a
+<p>The final action that the intent service must complete is sending the address back to a
{@link android.os.ResultReceiver} in the activity that started the service.
The {@link android.os.ResultReceiver} class allows you to send a
numeric result code as well as a message containing the result data. The
numeric code is useful for reporting the success or failure of the geocoding
request. In the case of a successful reverse geocoding, the message contains
the address. In the case of a failure, the message contains some text
- describing the reason for failure.</p>
+ describing the reason for the failure.</p>
<p>You have already retrieved the address from the geocoder, trapped any errors
- that may occur, and called the {@code deliverResultToReceiver()} method. Now
- you need to define the {@code deliverResultToReceiver()} method that sends
+ that may occur, and called the {@code deliverResultToReceiver()} method, so now
+ you must define the {@code deliverResultToReceiver()} method that sends
a result code and message bundle to the result receiver.</p>
<p>For the result code, use the value that you've passed to the
@@ -299,7 +301,7 @@
To construct the message bundle, concatenate the {@code RESULT_DATA_KEY}
constant from your {@code Constants} class (defined in
<a href="#retrieve-street-address">Retrieve the street address data</a>) and
- the value in the {@code message} parameter passed to the
+ the value in the {@code message} parameter that is passed to the
{@code deliverResultToReceiver()} method, as shown in the following sample:
</p>
@@ -315,26 +317,26 @@
}
</pre>
-<h2 id="start-intent">Start the Intent Service</h2>
+<h2 id="start-intent">Start the intent service</h2>
<p>The intent service, as defined in the previous section, runs in the
- background and is responsible for fetching the address corresponding to a
+ background and fetches the address corresponding to a
given geographic location. When you start the service, the Android framework
- instantiates and starts the service if it isn't already running, and creates a
- process if needed. If the service is already running then it remains running.
+ instantiates and starts the service if it isn't already running, and it creates a
+ process if needed. If the service is already running, it remains running.
Because the service extends {@link android.app.IntentService IntentService},
- it shuts down automatically when all intents have been processed.</p>
+ it shuts down automatically after all intents are processed.</p>
-<p>Start the service from your app's main activity,
+<p>Start the service from your app's main activity
and create an {@link android.content.Intent} to pass data to the service. You
- need an <em>explicit</em> intent, because you want only your service
+ need an <em>explicit</em> intent because you want only your service
to respond to the intent. For more information, see
<a href="{@docRoot}guide/components/intents-filters.html#Types">Intent
Types</a>.</p>
<p>To create an explicit intent, specify the name of the
class to use for the service: {@code FetchAddressIntentService.class}.
- Pass two pieces of information in the intent extras:</p>
+ Pass this information in the intent extras:</p>
<ul>
<li>A {@link android.os.ResultReceiver} to handle the results of the address
@@ -362,6 +364,12 @@
}
</pre>
+<p class="caution"><strong>Caution</strong>: To ensure that your app is secure, always use an
+explicit intent when starting a {@link android.app.Service} and do not declare intent filters for
+your services. Using an implicit intent to start a service is a security hazard because you cannot
+be certain of the service that will respond to the intent, and the user cannot see which service
+starts.</p>
+
<p>Call the above {@code startIntentService()} method when the
user takes an action that requires a geocoding address lookup. For example,
the user may press a <em>Fetch address</em> button on your app's UI. Before
@@ -391,7 +399,7 @@
app's UI. The following code snippet shows the call to the
{@code startIntentService()} method in the
<a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">{@code onConnected()}</a>
- callback provided by the Google API Client:</p>
+ callback that is provided by the Google API Client:</p>
<pre>
public class MainActivity extends ActionBarActivity implements
@@ -420,9 +428,9 @@
}
</pre>
-<h2 id="result-receiver">Receive the Geocoding Results</h2>
+<h2 id="result-receiver">Receive the geocoding results</h2>
-<p>The intent service has handled the geocoding request, and uses a
+<p>After the intent service handles the geocoding request, it uses a
{@link android.os.ResultReceiver} to return the results to the activity that
made the request. In the activity that makes the request, define an
{@code AddressResultReceiver} that extends {@link android.os.ResultReceiver}
@@ -430,14 +438,14 @@
<p>The result includes a numeric result code (<code>resultCode</code>) as well
as a message containing the result data (<code>resultData</code>). If the
- reverse geocoding process was successful, the <code>resultData</code> contains
+ reverse geocoding process is successful, the <code>resultData</code> contains
the address. In the case of a failure, the <code>resultData</code> contains
- text describing the reason for failure. For details of the possible errors,
+ text describing the reason for the failure. For details of the possible errors,
see <a href="#return-address">Return the address to the requestor</a>.</p>
<p>Override the
{@link android.os.ResultReceiver#onReceiveResult onReceiveResult()} method
- to handle the results delivered to the result receiver, as shown in the
+ to handle the results that are delivered to the result receiver, as shown in the
following code sample:</p>
<pre>
diff --git a/docs/html/training/run-background-service/index.jd b/docs/html/training/run-background-service/index.jd
index 22f3fc8..c48c681 100644
--- a/docs/html/training/run-background-service/index.jd
+++ b/docs/html/training/run-background-service/index.jd
@@ -35,16 +35,22 @@
<!-- ------------------------------------------------------------------------------------------- -->
<p>
Unless you specify otherwise, most of the operations you do in an app run in the foreground on
- a special thread called the UI thread. This can cause problems, because long-running operations
- will interfere with the responsiveness of your user interface. This annoys your users, and can
+ a special thread called the UI thread. Long-running foreground operations can cause problems
+ and interfere with the responsiveness of your user interface, which annoys your users and can
even cause system errors. To avoid this, the Android framework offers several classes that
- help you off-load operations onto a separate thread running in the background. The most useful
- of these is {@link android.app.IntentService}.
+ help you off-load operations onto a separate thread that runs in the background. The most
+ useful of these is {@link android.app.IntentService}.
</p>
<p>
This class describes how to implement an {@link android.app.IntentService}, send it work
requests, and report its results to other components.
</p>
+
+<p class="note"><strong>Note:</strong> If your app targets Android 5.0 (API level 21),
+ you should use {@link android.app.job.JobScheduler} to execute background
+ services. For more information about this class,
+ see the {@link android.app.job.JobScheduler} reference documentation.</p>
+
<h2>Lessons</h2>
<dl>
<dt>
diff --git a/docs/html/training/tv/tif/tvinput.jd b/docs/html/training/tv/tif/tvinput.jd
index 1a53398..2153ef8 100644
--- a/docs/html/training/tv/tif/tvinput.jd
+++ b/docs/html/training/tv/tif/tvinput.jd
@@ -10,9 +10,8 @@
<div id="tb">
<h2>This lesson teaches you to</h2>
<ol>
- <li><a href="#manifest">Declare Your TV Input Service in the Manifest</a></li>
- <li><a href="#tvinput">Define Your TV Input Service</a></li>
- <li><a href="#setup">Define Your Setup Activity</a></li>
+ <li><a href="#TIFCompanion">Create a TV Input Service Using the TIF Companion Library</a></li>
+ <li><a href="#NoTIFCompanion">Create a TV Input Service Using the TIF Framework</a></li>
</ol>
<h2>You should also read</h2>
<ul>
@@ -30,14 +29,253 @@
</div>
<p>A TV input service represents a media stream source, and lets you present your media content in a
-linear, broadcast TV fashion as channels and programs. With the TV input service, you can provide
+linear, broadcast TV fashion as channels and programs. With a TV input service, you can provide
parental controls, program guide information, and content ratings. The TV input service works
-with the Android system TV app, developed for the device and immutable by third-party apps, which
-ultimately controls and presents content on the TV. See
+with the Android system TV app. This app ultimately controls and presents channel content
+on the TV. The system TV app is developed specifically for the device and immutable
+by third-party apps. For more information about the TV Input Framework (TIF)
+architecture and its components, see
<a class="external-link" href="http://source.android.com/devices/tv/index.html">
-TV Input Framework</a> for more information about the framework architecture and its components.</p>
+TV Input Framework</a>.</p>
-<p>To develop a TV input service, you implement the following components:</p>
+<h2 id="TIFCompanion">Create a TV Input Service Using the TIF Companion Library</h2>
+
+<p>
+The TIF Companion Library is a framework that provides extensible
+implementations of common TV input service features. Use the TIF Companion
+Library to quickly and easily create your own TV input service that follows
+best practices for Android TV.
+</p>
+
+<h3 id="build">Update your build.gradle file</h3>
+
+<p>
+To get started using the TIF Companion Library, add the following line to your
+app's <code>build.gradle</code> file:
+</p>
+
+<pre>
+compile 'com.google.android.media.tv.companionlibrary:1.0.0'
+</pre>
+
+<p>The TIF Companion Library is not currently part of the Android
+framework. It is distributed as part of the <a class="external-link"
+href="https://github.com/googlesamples/androidtv-sample-inputs">
+TV Input Service sample app</a>, and not with the Android SDK.
+</p>
+
+<h3 id="manifest">Declare your TV input service in the manifest</h3>
+
+<p>Your app must provide a {@link android.media.tv.TvInputService}-compatible
+service that the system uses to access your app. The TIF
+Companion Library provides the <code>BaseTvInputService</code> class, which
+provides a default implementation of {@link android.media.tv.TvInputService}
+that you can customize. Create a subclass of <code>BaseTvInputService</code>,
+and declare the subclass in your manifest as a service.</p>
+
+<p>Within the manifest declaration, specify the
+{@link android.Manifest.permission#BIND_TV_INPUT} permission to allow the
+service to connect the TV input to the system. A system service
+performs the binding and has the
+{@link android.Manifest.permission#BIND_TV_INPUT} permission.
+The system TV app sends requests to TV input services
+via the {@link android.media.tv.TvInputManager} interface.</p>
+
+<p>In your service declaration, include an intent filter that specifies
+{@link android.media.tv.TvInputService} as the action to perform with the
+intent. Also declare the service metadata as a separate XML resource. The
+service declaration, intent filter, and service metadata declaration are shown
+in the following example:</p>
+
+<pre>
+<service android:name=".rich.RichTvInputService"
+ android:label="@string/rich_input_label"
+ android:permission="android.permission.BIND_TV_INPUT">
+ <!-- Required filter used by the system to launch our account service. -->
+ <intent-filter>
+ <action android:name="android.media.tv.TvInputService" />
+ </intent-filter>
+ <!-- An XML file which describes this input. This provides pointers to
+ the RichTvInputSetupActivity to the system/TV app. -->
+ <meta-data
+ android:name="android.media.tv.input"
+ android:resource="@xml/richtvinputservice" />
+</service>
+</pre>
+
+<p>Define the service metadata in a separate XML file. The service
+metadata XML file must include a setup interface that describes the TV input's
+initial configuration and channel scan. The metadata file should also contain a
+flag stating whether or not users are able to record content. For more
+information on how to support recording content in your app, see
+<a href="{@docRoot}preview/features/tv-recording-api.html">TV Recording</a>.
+</p>
+
+<p>The service metadata file is located in the XML resources directory
+for your app and must match the name of the resource you declared in the
+manifest. Using the manifest entries from the previous example, you would
+create the XML file at <code>res/xml/richtvinputservice.xml</code>, with the
+following contents:</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
+ android:canRecord="true"
+ android:setupActivity="com.example.android.sampletvinput.rich.RichTvInputSetupActivity" />
+</pre>
+
+<h3 id="setup">Define channels and create your setup activity</h3>
+
+<p>Your TV input service must define at least one channel that users
+access via the system TV app. You should register your channels
+in the system database, and provide a setup activity that the system
+invokes when it cannot find a channel for your app.</p>
+
+<p>First, enable your app to read from and write to the system Electronic
+Programming Guide (EPG), whose data includes channels and programs available
+to the user. To enable your app to perform these actions, and sync with the
+EPG after device restart, add the following elements to your app manifest:</p>
+
+<pre>
+<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
+<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED "/>
+</pre>
+
+<p>Add the following element to ensure that your app shows up in the
+Google Play Store as an app that provides content channels in Android TV:</p>
+
+<pre>
+<uses-feature
+ android:name="android.software.live_tv"
+ android:required="true" />
+</pre>
+
+<p>Next, create a class which extends the <code>EpgSyncJobService</code>
+class. This abstract class makes it easy to create a job service that
+creates and updates channels in the system database.</p>
+
+<p>In your subclass, create and return your full list of channels in
+<code>getChannels()</code>. If your channels come from an XMLTV file,
+use the <code>XmlTvParser</code> class. Otherwise generate
+channels programmatically using the <code>Channel.Builder</code> class.
+</p>
+
+<p>For each channel, the system calls <code>getProgramsForChannel()</code>
+when it needs a list of programs that can be viewed within a given time window
+on the channel. Return a list of <code>Program</code> objects for the
+channel. Use the <code>XmlTvParser</code> class to obtain programs from an
+XMLTV file, or generate them programmatically using the
+<code>Program.Builder</code> class.</p>
+
+<p>For each <code>Program</code> object, use an
+<code>InternalProviderData</code> object to set program information such as the
+program's video type. If you only have a limited number of programs that you
+want the channel to repeat in a loop, use the
+<code>InternalProviderData.setRepeatable()</code> method with a value of
+<code>true</code> when setting information about your program.</p>
+
+<p>After you've implemented the job service, add it to your app manifest:</p>
+
+<pre>
+<service
+ android:name=".sync.SampleJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE"
+ android:exported="true" />
+</pre>
+
+<p>Finally, create a setup activity. Your setup activity should provide a way
+to sync channel and program data. One way to do this is for the user to do it
+via the UI in the activity. You might also have the app do it automatically
+when the activity starts. When the setup activity needs to sync channel and
+program info, the app should start the job service:</p>
+
+<pre>
+String inputId = getActivity().getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
+EpgSyncJobService.cancelAllSyncRequests(getActivity());
+EpgSyncJobService.requestImmediateSync(getActivity(), inputId,
+ new ComponentName(getActivity(), SampleJobService.class));
+</pre>
+
+<p>Use the <code>requestImmediateSync()</code> method to sync
+the job service. The user must wait for the sync to finish, so you should
+keep your request period relatively short.</p>
+
+<p>Use the <code>setUpPeriodicSync()</code> method to have the job service
+periodically sync channel and program data in the background:</p>
+
+<pre>
+EpgSyncJobService.setUpPeriodicSync(context, inputId,
+ new ComponentName(context, SampleJobService.class));
+</pre>
+
+<p>The TIF Companion Library provides an additional overloaded method of
+<code>requestImmediateSync()</code> that lets you specify the duration of
+channel data to sync in milliseconds. The default method syncs one hour's
+worth of channel data.
+</p>
+
+<p>The TIF Companion Library also provides an additional overloaded method of
+<code>setUpPeriodicSync()</code> that lets you specify the duration of
+channel data to sync, and how often the periodic sync should occur. The
+default method syncs 48 hours of channel data every 12 hours.
+</p>
+
+<p>For more details about channel data and the EPG, see
+<a href="{@docRoot}training/tv/tif/channel.html"> Working with Channel Data</a>.
+</p>
+
+<h3 id="playback">Handle tuning requests and media playback</h3>
+
+<p>When a user selects a specific channel, the system TV app uses a
+<code>Session</code>, created by your app, to tune to the requested channel
+and play content. The TIF Companion Library provides several
+classes you can extend to handle channel and session calls from the system.</p>
+
+<p>Your <code>BaseTvInputService</code> subclass creates sessions which handle
+tuning requests. Override the
+<code>onCreateSession()</code> method, create a session extended from
+the <code>BaseTvInputService.Session</code> class, and call
+<code>super.sessionCreated()</code> with your new session. In the following
+example, <code>onCreateSession()</code> returns a
+<code>RichTvInputSessionImpl</code> object that extends
+<code>BaseTvInputService.Session</code>:</p>
+
+<pre>
+@Override
+public final Session onCreateSession(String inputId) {
+ RichTvInputSessionImpl session = new RichTvInputSessionImpl(this, inputId);
+ session.setOverlayViewEnabled(true);
+ return super.sessionCreated(session);
+}
+</pre>
+
+<p>When the user uses the system TV app to start viewing one of your channels,
+the system calls your session's <code>onPlayChannel()</code> method. Override
+this method if you need to do any special channel initialization before the
+program starts playing.</p>
+
+<p>The system then obtains the currently scheduled program and calls your
+session's <code>onPlayProgram()</code> method, specifying the program
+information and start time in milliseconds. Use the
+<code>TvPlayer</code> interface to start playing the program.</p>
+
+<p>Your media player code should implement <code>TvPlayer</code> to handle
+specific playback events. The <code>TvPlayer</code> class handles features
+like time-shifting controls without adding complexity to your
+<code>BaseTvInputService</code> implementation.</p>
+
+<p>In your session's <code>getTvPlayer()</code> method, return
+your media player that implements <code>TvPlayer</code>. The
+<a class="external-link"
+href="https://github.com/googlesamples/androidtv-sample-inputs">
+TV Input Service sample app</a> implements a media player that uses
+<a href="{@docRoot}guide/topics/media/exoplayer.html">ExoPlayer</a>.</p>
+
+<h2 id="NoTIFCompanion">Create a TV Input Service Using the TIF Framework</h2>
+
+<p>If your TV input service can't use the TIF Companion Library, you need
+to implement the following components:</p>
<ul>
<li>{@link android.media.tv.TvInputService} provides long-running and background availability for
@@ -56,43 +294,18 @@
the interaction with TV inputs and apps</li>
</ul>
-<h2 id="manifest">Declare Your TV Input Service in the Manifest</h2>
+<p>You also need to do the following:</p>
-<p>Your app manifest must declare your {@link android.media.tv.TvInputService}. Within that
-declaration, specify the {@link android.Manifest.permission#BIND_TV_INPUT} permission to allow the
-service to connect the TV input to the system. A system service (<code>TvInputManagerService</code>)
-performs the binding and has that permission. The system TV app sends requests to TV input services
-via the {@link android.media.tv.TvInputManager} interface. The service declaration must also
-include an intent filter that specifies the {@link android.media.tv.TvInputService}
-as the action to perform with the intent. Also within the service declaration, declare the service
-meta data in a separate XML resource. The service declaration, the intent filter and the service
-meta data are described in the following example.</p>
+<ol>
+<li>Declare your TV input service in the manifest, as
+described in <a href="#manifest">Declare your TV input service in the
+manifest</a>.</li>
+<li>Create the service metadata file.</li>
+<li>Create and register your channel and program information.</li>
+<li>Create your setup activity.</li>
+</ol>
-<pre>
-<service android:name="com.example.sampletvinput.SampleTvInput"
- android:label="@string/sample_tv_input_label"
- android:permission="android.permission.BIND_TV_INPUT">
- <intent-filter>
- <action android:name="android.media.tv.TvInputService" />
- </intent-filter>
- <meta-data android:name="android.media.tv.input"
- android:resource="@xml/sample_tv_input" />
-</service>
-</pre>
-
-<p>Define the service meta data in separate XML file, as shown in the following example. The service
-meta data must include a setup interface that describes the TV input's initial configuration and
-channel scan. The service meta data file is located in the XML resources directory
-for your application and must match the name of the resource in the manifest. Using the example
-manifest entries above, you would create an XML file in the location
-<code>res/xml/sample_tv_input.xml</code>, with the following contents:</p>
-
-<pre>
-<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
- android:setupActivity="com.example.sampletvinput.SampleTvInputSetupActivity" />
-</pre>
-
-<h2 id="tvinput">Define Your TV Input Service</h2>
+<h3 id="tvinput">Define your TV input service</h3>
<div class="figure">
<img id="tvinputlife" src="{@docRoot}images/tv/tvinput-life.png" alt=""/>
@@ -102,7 +315,7 @@
<p>For your service, you extend the {@link android.media.tv.TvInputService} class. A
{@link android.media.tv.TvInputService} implementation is a
<a href="{@docRoot}guide/components/bound-services.html">bound service</a> where the system service
-(<code>TvInputManagerService</code>) is the client that binds to it. The service life cycle methods
+is the client that binds to it. The service life cycle methods
you need to implement are illustrated in figure 1.</p>
<p>The {@link android.app.Service#onCreate()} method initializes and starts the
@@ -145,7 +358,8 @@
<p>The {@link android.media.tv.TvInputService} creates a
{@link android.media.tv.TvInputService.Session} that implements {@link android.os.Handler.Callback}
-to handle player state changes. With {@link android.media.tv.TvInputService.Session#onSetSurface(android.view.Surface) onSetSurface()},
+to handle player state changes. With
+{@link android.media.tv.TvInputService.Session#onSetSurface(android.view.Surface) onSetSurface()},
the {@link android.media.tv.TvInputService.Session} sets the {@link android.view.Surface} with the
video content. See <a href="{@docRoot}training/tv/tif/ui.html#surface">Integrate Player with Surface</a>
for more information about working with {@link android.view.Surface} to render video.</p>
@@ -153,16 +367,16 @@
<p>The {@link android.media.tv.TvInputService.Session} handles the
{@link android.media.tv.TvInputService.Session#onTune(android.net.Uri) onTune()}
event when the user selects a channel, and notifies the system TV app for changes in the content and
-content meta data. These <code>notify()</code> methods are described in
+content metadata. These <code>notify()</code> methods are described in
<a href="{@docRoot}training/tv/tif/ui.html#control">
Control Content</a> and <a href="{@docRoot}training/tv/tif/ui.html#track">Handle Track Selection</a>
further in this training.</p>
-<h2 id="setup">Define Your Setup Activity</h2>
+<h3 id="setup">Define your setup activity</h3>
<p>The system TV app works with the setup activity you define for your TV input. The
setup activity is required and must provide at least one channel record for the system database. The
-system TV app will invoke the setup activity when it cannot find a channel for the TV input.
+system TV app invokes the setup activity when it cannot find a channel for the TV input.
<p>The setup activity describes to the system TV app the channels made available through the TV
input, as demonstrated in the next lesson, <a href="{@docRoot}training/tv/tif/channel.html">Creating
-and Updating Channel Data</a>.</p>
+and Updating Channel Data</a>.</p>
\ No newline at end of file
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 3f0bed8..b093458 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -979,12 +979,11 @@
/**
* Draw the picture, stretched to fit into the dst rectangle.
*/
- public void drawPicture(@NonNull Picture picture, @NonNull Rect dst) {
+ public void drawPicture(@NonNull Picture picture, @NonNull RectF dst) {
save();
translate(dst.left, dst.top);
if (picture.getWidth() > 0 && picture.getHeight() > 0) {
- scale((float) dst.width() / picture.getWidth(),
- (float) dst.height() / picture.getHeight());
+ scale(dst.width() / picture.getWidth(), dst.height() / picture.getHeight());
}
drawPicture(picture);
restore();
@@ -993,11 +992,12 @@
/**
* Draw the picture, stretched to fit into the dst rectangle.
*/
- public void drawPicture(@NonNull Picture picture, @NonNull RectF dst) {
+ public void drawPicture(@NonNull Picture picture, @NonNull Rect dst) {
save();
translate(dst.left, dst.top);
if (picture.getWidth() > 0 && picture.getHeight() > 0) {
- scale(dst.width() / picture.getWidth(), dst.height() / picture.getHeight());
+ scale((float) dst.width() / picture.getWidth(),
+ (float) dst.height() / picture.getHeight());
}
drawPicture(picture);
restore();
@@ -1136,53 +1136,29 @@
float right, float bottom);
/**
- * <p>Draw the specified arc, which will be scaled to fit inside the
- * specified oval.</p>
+ * <p>
+ * Draw the specified arc, which will be scaled to fit inside the specified oval.
+ * </p>
+ * <p>
+ * If the start angle is negative or >= 360, the start angle is treated as start angle modulo
+ * 360.
+ * </p>
+ * <p>
+ * If the sweep angle is >= 360, then the oval is drawn completely. Note that this differs
+ * slightly from SkPath::arcTo, which treats the sweep angle modulo 360. If the sweep angle is
+ * negative, the sweep angle is treated as sweep angle modulo 360
+ * </p>
+ * <p>
+ * The arc is drawn clockwise. An angle of 0 degrees correspond to the geometric angle of 0
+ * degrees (3 o'clock on a watch.)
+ * </p>
*
- * <p>If the start angle is negative or >= 360, the start angle is treated
- * as start angle modulo 360.</p>
- *
- * <p>If the sweep angle is >= 360, then the oval is drawn
- * completely. Note that this differs slightly from SkPath::arcTo, which
- * treats the sweep angle modulo 360. If the sweep angle is negative,
- * the sweep angle is treated as sweep angle modulo 360</p>
- *
- * <p>The arc is drawn clockwise. An angle of 0 degrees correspond to the
- * geometric angle of 0 degrees (3 o'clock on a watch.)</p>
- *
+ * @param oval The bounds of oval used to define the shape and size of the arc
* @param startAngle Starting angle (in degrees) where the arc begins
* @param sweepAngle Sweep angle (in degrees) measured clockwise
- * @param useCenter If true, include the center of the oval in the arc, and
- close it if it is being stroked. This will draw a wedge
- * @param paint The paint used to draw the arc
- */
- public void drawArc(float left, float top, float right, float bottom, float startAngle,
- float sweepAngle, boolean useCenter, @NonNull Paint paint) {
- super.drawArc(left, top, right, bottom, startAngle, sweepAngle, useCenter, paint);
- }
-
- /**
- * <p>Draw the specified arc, which will be scaled to fit inside the
- * specified oval.</p>
- *
- * <p>If the start angle is negative or >= 360, the start angle is treated
- * as start angle modulo 360.</p>
- *
- * <p>If the sweep angle is >= 360, then the oval is drawn
- * completely. Note that this differs slightly from SkPath::arcTo, which
- * treats the sweep angle modulo 360. If the sweep angle is negative,
- * the sweep angle is treated as sweep angle modulo 360</p>
- *
- * <p>The arc is drawn clockwise. An angle of 0 degrees correspond to the
- * geometric angle of 0 degrees (3 o'clock on a watch.)</p>
- *
- * @param oval The bounds of oval used to define the shape and size
- * of the arc
- * @param startAngle Starting angle (in degrees) where the arc begins
- * @param sweepAngle Sweep angle (in degrees) measured clockwise
- * @param useCenter If true, include the center of the oval in the arc, and
- close it if it is being stroked. This will draw a wedge
- * @param paint The paint used to draw the arc
+ * @param useCenter If true, include the center of the oval in the arc, and close it if it is
+ * being stroked. This will draw a wedge
+ * @param paint The paint used to draw the arc
*/
public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
@NonNull Paint paint) {
@@ -1190,8 +1166,37 @@
}
/**
- * Fill the entire canvas' bitmap (restricted to the current clip) with the
- * specified ARGB color, using srcover porterduff mode.
+ * <p>
+ * Draw the specified arc, which will be scaled to fit inside the specified oval.
+ * </p>
+ * <p>
+ * If the start angle is negative or >= 360, the start angle is treated as start angle modulo
+ * 360.
+ * </p>
+ * <p>
+ * If the sweep angle is >= 360, then the oval is drawn completely. Note that this differs
+ * slightly from SkPath::arcTo, which treats the sweep angle modulo 360. If the sweep angle is
+ * negative, the sweep angle is treated as sweep angle modulo 360
+ * </p>
+ * <p>
+ * The arc is drawn clockwise. An angle of 0 degrees correspond to the geometric angle of 0
+ * degrees (3 o'clock on a watch.)
+ * </p>
+ *
+ * @param startAngle Starting angle (in degrees) where the arc begins
+ * @param sweepAngle Sweep angle (in degrees) measured clockwise
+ * @param useCenter If true, include the center of the oval in the arc, and close it if it is
+ * being stroked. This will draw a wedge
+ * @param paint The paint used to draw the arc
+ */
+ public void drawArc(float left, float top, float right, float bottom, float startAngle,
+ float sweepAngle, boolean useCenter, @NonNull Paint paint) {
+ super.drawArc(left, top, right, bottom, startAngle, sweepAngle, useCenter, paint);
+ }
+
+ /**
+ * Fill the entire canvas' bitmap (restricted to the current clip) with the specified ARGB
+ * color, using srcover porterduff mode.
*
* @param a alpha component (0..255) of the color to draw onto the canvas
* @param r red component (0..255) of the color to draw onto the canvas
@@ -1203,60 +1208,68 @@
}
/**
- * Draw the specified bitmap, with its top/left corner at (x,y), using
- * the specified paint, transformed by the current matrix.
- *
- * <p>Note: if the paint contains a maskfilter that generates a mask which
- * extends beyond the bitmap's original width/height (e.g. BlurMaskFilter),
- * then the bitmap will be drawn as if it were in a Shader with CLAMP mode.
- * Thus the color outside of the original width/height will be the edge
- * color replicated.
- *
- * <p>If the bitmap and canvas have different densities, this function
- * will take care of automatically scaling the bitmap to draw at the
- * same density as the canvas.
+ * Draw the specified bitmap, with its top/left corner at (x,y), using the specified paint,
+ * transformed by the current matrix.
+ * <p>
+ * Note: if the paint contains a maskfilter that generates a mask which extends beyond the
+ * bitmap's original width/height (e.g. BlurMaskFilter), then the bitmap will be drawn as if it
+ * were in a Shader with CLAMP mode. Thus the color outside of the original width/height will be
+ * the edge color replicated.
+ * <p>
+ * If the bitmap and canvas have different densities, this function will take care of
+ * automatically scaling the bitmap to draw at the same density as the canvas.
*
* @param bitmap The bitmap to be drawn
- * @param left The position of the left side of the bitmap being drawn
- * @param top The position of the top side of the bitmap being drawn
- * @param paint The paint used to draw the bitmap (may be null)
+ * @param left The position of the left side of the bitmap being drawn
+ * @param top The position of the top side of the bitmap being drawn
+ * @param paint The paint used to draw the bitmap (may be null)
*/
public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) {
super.drawBitmap(bitmap, left, top, paint);
}
/**
- * Draw the bitmap using the specified matrix.
+ * Draw the specified bitmap, scaling/translating automatically to fill the destination
+ * rectangle. If the source rectangle is not null, it specifies the subset of the bitmap to
+ * draw.
+ * <p>
+ * Note: if the paint contains a maskfilter that generates a mask which extends beyond the
+ * bitmap's original width/height (e.g. BlurMaskFilter), then the bitmap will be drawn as if it
+ * were in a Shader with CLAMP mode. Thus the color outside of the original width/height will be
+ * the edge color replicated.
+ * <p>
+ * This function <em>ignores the density associated with the bitmap</em>. This is because the
+ * source and destination rectangle coordinate spaces are in their respective densities, so must
+ * already have the appropriate scaling factor applied.
*
- * @param bitmap The bitmap to draw
- * @param matrix The matrix used to transform the bitmap when it is drawn
- * @param paint May be null. The paint used to draw the bitmap
+ * @param bitmap The bitmap to be drawn
+ * @param src May be null. The subset of the bitmap to be drawn
+ * @param dst The rectangle that the bitmap will be scaled/translated to fit into
+ * @param paint May be null. The paint used to draw the bitmap
*/
- public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) {
- super.drawBitmap(bitmap, matrix, paint);
+ public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst,
+ @Nullable Paint paint) {
+ super.drawBitmap(bitmap, src, dst, paint);
}
/**
- * Draw the specified bitmap, scaling/translating automatically to fill
- * the destination rectangle. If the source rectangle is not null, it
- * specifies the subset of the bitmap to draw.
- *
- * <p>Note: if the paint contains a maskfilter that generates a mask which
- * extends beyond the bitmap's original width/height (e.g. BlurMaskFilter),
- * then the bitmap will be drawn as if it were in a Shader with CLAMP mode.
- * Thus the color outside of the original width/height will be the edge
- * color replicated.
- *
- * <p>This function <em>ignores the density associated with the bitmap</em>.
- * This is because the source and destination rectangle coordinate
- * spaces are in their respective densities, so must already have the
- * appropriate scaling factor applied.
+ * Draw the specified bitmap, scaling/translating automatically to fill the destination
+ * rectangle. If the source rectangle is not null, it specifies the subset of the bitmap to
+ * draw.
+ * <p>
+ * Note: if the paint contains a maskfilter that generates a mask which extends beyond the
+ * bitmap's original width/height (e.g. BlurMaskFilter), then the bitmap will be drawn as if it
+ * were in a Shader with CLAMP mode. Thus the color outside of the original width/height will be
+ * the edge color replicated.
+ * <p>
+ * This function <em>ignores the density associated with the bitmap</em>. This is because the
+ * source and destination rectangle coordinate spaces are in their respective densities, so must
+ * already have the appropriate scaling factor applied.
*
* @param bitmap The bitmap to be drawn
- * @param src May be null. The subset of the bitmap to be drawn
- * @param dst The rectangle that the bitmap will be scaled/translated
- * to fit into
- * @param paint May be null. The paint used to draw the bitmap
+ * @param src May be null. The subset of the bitmap to be drawn
+ * @param dst The rectangle that the bitmap will be scaled/translated to fit into
+ * @param paint May be null. The paint used to draw the bitmap
*/
public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst,
@Nullable Paint paint) {
@@ -1264,55 +1277,25 @@
}
/**
- * Draw the specified bitmap, scaling/translating automatically to fill
- * the destination rectangle. If the source rectangle is not null, it
- * specifies the subset of the bitmap to draw.
- *
- * <p>Note: if the paint contains a maskfilter that generates a mask which
- * extends beyond the bitmap's original width/height (e.g. BlurMaskFilter),
- * then the bitmap will be drawn as if it were in a Shader with CLAMP mode.
- * Thus the color outside of the original width/height will be the edge
- * color replicated.
- *
- * <p>This function <em>ignores the density associated with the bitmap</em>.
- * This is because the source and destination rectangle coordinate
- * spaces are in their respective densities, so must already have the
- * appropriate scaling factor applied.
- *
- * @param bitmap The bitmap to be drawn
- * @param src May be null. The subset of the bitmap to be drawn
- * @param dst The rectangle that the bitmap will be scaled/translated
- * to fit into
- * @param paint May be null. The paint used to draw the bitmap
- */
- public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst,
- @Nullable Paint paint) {
- super.drawBitmap(bitmap, src, dst, paint);
- }
-
- /**
- * Treat the specified array of colors as a bitmap, and draw it. This gives
- * the same result as first creating a bitmap from the array, and then
- * drawing it, but this method avoids explicitly creating a bitmap object
- * which can be more efficient if the colors are changing often.
+ * Treat the specified array of colors as a bitmap, and draw it. This gives the same result as
+ * first creating a bitmap from the array, and then drawing it, but this method avoids
+ * explicitly creating a bitmap object which can be more efficient if the colors are changing
+ * often.
*
* @param colors Array of colors representing the pixels of the bitmap
* @param offset Offset into the array of colors for the first pixel
- * @param stride The number of colors in the array between rows (must be
- * >= width or <= -width).
+ * @param stride The number of colors in the array between rows (must be >= width or <= -width).
* @param x The X coordinate for where to draw the bitmap
* @param y The Y coordinate for where to draw the bitmap
* @param width The width of the bitmap
* @param height The height of the bitmap
- * @param hasAlpha True if the alpha channel of the colors contains valid
- * values. If false, the alpha byte is ignored (assumed to
- * be 0xFF for every pixel).
- * @param paint May be null. The paint used to draw the bitmap
- *
+ * @param hasAlpha True if the alpha channel of the colors contains valid values. If false, the
+ * alpha byte is ignored (assumed to be 0xFF for every pixel).
+ * @param paint May be null. The paint used to draw the bitmap
* @deprecated Usage with a {@link #isHardwareAccelerated() hardware accelerated} canvas
- * requires an internal copy of color buffer contents every time this method is called. Using a
- * Bitmap avoids this copy, and allows the application to more explicitly control the lifetime
- * and copies of pixel data.
+ * requires an internal copy of color buffer contents every time this method is
+ * called. Using a Bitmap avoids this copy, and allows the application to more
+ * explicitly control the lifetime and copies of pixel data.
*/
@Deprecated
public void drawBitmap(@NonNull int[] colors, int offset, int stride, float x, float y,
@@ -1324,9 +1307,9 @@
* Legacy version of drawBitmap(int[] colors, ...) that took ints for x,y
*
* @deprecated Usage with a {@link #isHardwareAccelerated() hardware accelerated} canvas
- * requires an internal copy of color buffer contents every time this method is called. Using a
- * Bitmap avoids this copy, and allows the application to more explicitly control the lifetime
- * and copies of pixel data.
+ * requires an internal copy of color buffer contents every time this method is
+ * called. Using a Bitmap avoids this copy, and allows the application to more
+ * explicitly control the lifetime and copies of pixel data.
*/
@Deprecated
public void drawBitmap(@NonNull int[] colors, int offset, int stride, int x, int y,
@@ -1335,30 +1318,35 @@
}
/**
- * Draw the bitmap through the mesh, where mesh vertices are evenly
- * distributed across the bitmap. There are meshWidth+1 vertices across, and
- * meshHeight+1 vertices down. The verts array is accessed in row-major
- * order, so that the first meshWidth+1 vertices are distributed across the
- * top of the bitmap from left to right. A more general version of this
- * method is drawVertices().
+ * Draw the bitmap using the specified matrix.
+ *
+ * @param bitmap The bitmap to draw
+ * @param matrix The matrix used to transform the bitmap when it is drawn
+ * @param paint May be null. The paint used to draw the bitmap
+ */
+ public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) {
+ super.drawBitmap(bitmap, matrix, paint);
+ }
+
+ /**
+ * Draw the bitmap through the mesh, where mesh vertices are evenly distributed across the
+ * bitmap. There are meshWidth+1 vertices across, and meshHeight+1 vertices down. The verts
+ * array is accessed in row-major order, so that the first meshWidth+1 vertices are distributed
+ * across the top of the bitmap from left to right. A more general version of this method is
+ * drawVertices().
*
* @param bitmap The bitmap to draw using the mesh
- * @param meshWidth The number of columns in the mesh. Nothing is drawn if
- * this is 0
- * @param meshHeight The number of rows in the mesh. Nothing is drawn if
- * this is 0
- * @param verts Array of x,y pairs, specifying where the mesh should be
- * drawn. There must be at least
- * (meshWidth+1) * (meshHeight+1) * 2 + vertOffset values
- * in the array
+ * @param meshWidth The number of columns in the mesh. Nothing is drawn if this is 0
+ * @param meshHeight The number of rows in the mesh. Nothing is drawn if this is 0
+ * @param verts Array of x,y pairs, specifying where the mesh should be drawn. There must be at
+ * least (meshWidth+1) * (meshHeight+1) * 2 + vertOffset values in the array
* @param vertOffset Number of verts elements to skip before drawing
- * @param colors May be null. Specifies a color at each vertex, which is
- * interpolated across the cell, and whose values are
- * multiplied by the corresponding bitmap colors. If not null,
- * there must be at least (meshWidth+1) * (meshHeight+1) +
- * colorOffset values in the array.
+ * @param colors May be null. Specifies a color at each vertex, which is interpolated across the
+ * cell, and whose values are multiplied by the corresponding bitmap colors. If not
+ * null, there must be at least (meshWidth+1) * (meshHeight+1) + colorOffset values
+ * in the array.
* @param colorOffset Number of color elements to skip before drawing
- * @param paint May be null. The paint used to draw the bitmap
+ * @param paint May be null. The paint used to draw the bitmap
*/
public void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
@NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
@@ -1368,22 +1356,21 @@
}
/**
- * Draw the specified circle using the specified paint. If radius is <= 0,
- * then nothing will be drawn. The circle will be filled or framed based
- * on the Style in the paint.
+ * Draw the specified circle using the specified paint. If radius is <= 0, then nothing will be
+ * drawn. The circle will be filled or framed based on the Style in the paint.
*
- * @param cx The x-coordinate of the center of the cirle to be drawn
- * @param cy The y-coordinate of the center of the cirle to be drawn
+ * @param cx The x-coordinate of the center of the cirle to be drawn
+ * @param cy The y-coordinate of the center of the cirle to be drawn
* @param radius The radius of the cirle to be drawn
- * @param paint The paint used to draw the circle
+ * @param paint The paint used to draw the circle
*/
public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) {
super.drawCircle(cx, cy, radius, paint);
}
/**
- * Fill the entire canvas' bitmap (restricted to the current clip) with the
- * specified color, using srcover porterduff mode.
+ * Fill the entire canvas' bitmap (restricted to the current clip) with the specified color,
+ * using srcover porterduff mode.
*
* @param color the color to draw onto the canvas
*/
@@ -1392,27 +1379,29 @@
}
/**
- * Fill the entire canvas' bitmap (restricted to the current clip) with the
- * specified color and porter-duff xfermode.
+ * Fill the entire canvas' bitmap (restricted to the current clip) with the specified color and
+ * porter-duff xfermode.
*
* @param color the color to draw with
- * @param mode the porter-duff mode to apply to the color
+ * @param mode the porter-duff mode to apply to the color
*/
public void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
super.drawColor(color, mode);
}
/**
- * Draw a line segment with the specified start and stop x,y coordinates,
- * using the specified paint.
- *
- * <p>Note that since a line is always "framed", the Style is ignored in the paint.</p>
- *
- * <p>Degenerate lines (length is 0) will not be drawn.</p>
+ * Draw a line segment with the specified start and stop x,y coordinates, using the specified
+ * paint.
+ * <p>
+ * Note that since a line is always "framed", the Style is ignored in the paint.
+ * </p>
+ * <p>
+ * Degenerate lines (length is 0) will not be drawn.
+ * </p>
*
* @param startX The x-coordinate of the start point of the line
* @param startY The y-coordinate of the start point of the line
- * @param paint The paint used to draw the line
+ * @param paint The paint used to draw the line
*/
public void drawLine(float startX, float startY, float stopX, float stopY,
@NonNull Paint paint) {
@@ -1420,40 +1409,30 @@
}
/**
- * Draw a series of lines. Each line is taken from 4 consecutive values
- * in the pts array. Thus to draw 1 line, the array must contain at least 4
- * values. This is logically the same as drawing the array as follows:
- * drawLine(pts[0], pts[1], pts[2], pts[3]) followed by
+ * Draw a series of lines. Each line is taken from 4 consecutive values in the pts array. Thus
+ * to draw 1 line, the array must contain at least 4 values. This is logically the same as
+ * drawing the array as follows: drawLine(pts[0], pts[1], pts[2], pts[3]) followed by
* drawLine(pts[4], pts[5], pts[6], pts[7]) and so on.
*
- * @param pts Array of points to draw [x0 y0 x1 y1 x2 y2 ...]
- * @param offset Number of values in the array to skip before drawing.
- * @param count The number of values in the array to process, after
- * skipping "offset" of them. Since each line uses 4 values,
- * the number of "lines" that are drawn is really
- * (count >> 2).
- * @param paint The paint used to draw the points
+ * @param pts Array of points to draw [x0 y0 x1 y1 x2 y2 ...]
+ * @param offset Number of values in the array to skip before drawing.
+ * @param count The number of values in the array to process, after skipping "offset" of them.
+ * Since each line uses 4 values, the number of "lines" that are drawn is really
+ * (count >> 2).
+ * @param paint The paint used to draw the points
*/
- public void drawLines(@Size(multiple=4) @NonNull float[] pts, int offset, int count,
+ public void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count,
@NonNull Paint paint) {
super.drawLines(pts, offset, count, paint);
}
- public void drawLines(@Size(multiple=4) @NonNull float[] pts, @NonNull Paint paint) {
+ public void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint) {
super.drawLines(pts, paint);
}
/**
- * Draw the specified oval using the specified paint. The oval will be
- * filled or framed based on the Style in the paint.
- */
- public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint) {
- super.drawOval(left, top, right, bottom, paint);
- }
-
- /**
- * Draw the specified oval using the specified paint. The oval will be
- * filled or framed based on the Style in the paint.
+ * Draw the specified oval using the specified paint. The oval will be filled or framed based on
+ * the Style in the paint.
*
* @param oval The rectangle bounds of the oval to be drawn
*/
@@ -1462,9 +1441,17 @@
}
/**
- * Fill the entire canvas' bitmap (restricted to the current clip) with
- * the specified paint. This is equivalent (but faster) to drawing an
- * infinitely large rectangle with the specified paint.
+ * Draw the specified oval using the specified paint. The oval will be filled or framed based on
+ * the Style in the paint.
+ */
+ public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint) {
+ super.drawOval(left, top, right, bottom, paint);
+ }
+
+ /**
+ * Fill the entire canvas' bitmap (restricted to the current clip) with the specified paint.
+ * This is equivalent (but faster) to drawing an infinitely large rectangle with the specified
+ * paint.
*
* @param paint The paint used to draw onto the canvas
*/
@@ -1478,7 +1465,6 @@
* @param patch The ninepatch object to render
* @param dst The destination rectangle.
* @param paint The paint to draw the bitmap with. may be null
- *
* @hide
*/
public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) {
@@ -1491,7 +1477,6 @@
* @param patch The ninepatch object to render
* @param dst The destination rectangle.
* @param paint The paint to draw the bitmap with. may be null
- *
* @hide
*/
public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) {
@@ -1499,10 +1484,10 @@
}
/**
- * Draw the specified path using the specified paint. The path will be
- * filled or framed based on the Style in the paint.
+ * Draw the specified path using the specified paint. The path will be filled or framed based on
+ * the Style in the paint.
*
- * @param path The path to be drawn
+ * @param path The path to be drawn
* @param paint The paint used to draw the path
*/
public void drawPath(@NonNull Path path, @NonNull Paint paint) {
@@ -1517,22 +1502,19 @@
}
/**
- * Draw a series of points. Each point is centered at the coordinate
- * specified by pts[], and its diameter is specified by the paint's stroke
- * width (as transformed by the canvas' CTM), with special treatment for
- * a stroke width of 0, which always draws exactly 1 pixel (or at most 4
- * if antialiasing is enabled). The shape of the point is controlled by
- * the paint's Cap type. The shape is a square, unless the cap type is
- * Round, in which case the shape is a circle.
+ * Draw a series of points. Each point is centered at the coordinate specified by pts[], and its
+ * diameter is specified by the paint's stroke width (as transformed by the canvas' CTM), with
+ * special treatment for a stroke width of 0, which always draws exactly 1 pixel (or at most 4
+ * if antialiasing is enabled). The shape of the point is controlled by the paint's Cap type.
+ * The shape is a square, unless the cap type is Round, in which case the shape is a circle.
*
- * @param pts Array of points to draw [x0 y0 x1 y1 x2 y2 ...]
- * @param offset Number of values to skip before starting to draw.
- * @param count The number of values to process, after skipping offset
- * of them. Since one point uses two values, the number of
- * "points" that are drawn is really (count >> 1).
- * @param paint The paint used to draw the points
+ * @param pts Array of points to draw [x0 y0 x1 y1 x2 y2 ...]
+ * @param offset Number of values to skip before starting to draw.
+ * @param count The number of values to process, after skipping offset of them. Since one point
+ * uses two values, the number of "points" that are drawn is really (count >> 1).
+ * @param paint The paint used to draw the points
*/
- public void drawPoints(@Size(multiple=2) float[] pts, int offset, int count,
+ public void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count,
@NonNull Paint paint) {
super.drawPoints(pts, offset, count, paint);
}
@@ -1540,80 +1522,50 @@
/**
* Helper for drawPoints() that assumes you want to draw the entire array
*/
- public void drawPoints(@Size(multiple=2) @NonNull float[] pts, @NonNull Paint paint) {
+ public void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint) {
super.drawPoints(pts, paint);
}
/**
- * Draw the text in the array, with each character's origin specified by
- * the pos array.
+ * Draw the text in the array, with each character's origin specified by the pos array.
*
- * @param text The text to be drawn
- * @param index The index of the first character to draw
- * @param count The number of characters to draw, starting from index.
- * @param pos Array of [x,y] positions, used to position each
- * character
- * @param paint The paint used for the text (e.g. color, size, style)
- *
- * @deprecated This method does not support glyph composition and decomposition and
- * should therefore not be used to render complex scripts. It also doesn't
- * handle supplementary characters (eg emoji).
+ * @param text The text to be drawn
+ * @param index The index of the first character to draw
+ * @param count The number of characters to draw, starting from index.
+ * @param pos Array of [x,y] positions, used to position each character
+ * @param paint The paint used for the text (e.g. color, size, style)
+ * @deprecated This method does not support glyph composition and decomposition and should
+ * therefore not be used to render complex scripts. It also doesn't handle
+ * supplementary characters (eg emoji).
*/
@Deprecated
public void drawPosText(@NonNull char[] text, int index, int count,
- @NonNull @Size(multiple=2) float[] pos,
+ @NonNull @Size(multiple = 2) float[] pos,
@NonNull Paint paint) {
super.drawPosText(text, index, count, pos, paint);
}
/**
- * Draw the text in the array, with each character's origin specified by
- * the pos array.
+ * Draw the text in the array, with each character's origin specified by the pos array.
*
- * @param text The text to be drawn
- * @param pos Array of [x,y] positions, used to position each character
+ * @param text The text to be drawn
+ * @param pos Array of [x,y] positions, used to position each character
* @param paint The paint used for the text (e.g. color, size, style)
- *
- * @deprecated This method does not support glyph composition and decomposition and
- * should therefore not be used to render complex scripts. It also doesn't
- * handle supplementary characters (eg emoji).
+ * @deprecated This method does not support glyph composition and decomposition and should
+ * therefore not be used to render complex scripts. It also doesn't handle
+ * supplementary characters (eg emoji).
*/
@Deprecated
- public void drawPosText(@NonNull String text, @NonNull @Size(multiple=2) float[] pos,
+ public void drawPosText(@NonNull String text, @NonNull @Size(multiple = 2) float[] pos,
@NonNull Paint paint) {
super.drawPosText(text, pos, paint);
}
/**
- * Draw the specified Rect using the specified paint. The rectangle will
- * be filled or framed based on the Style in the paint.
+ * Draw the specified Rect using the specified paint. The rectangle will be filled or framed
+ * based on the Style in the paint.
*
- * @param left The left side of the rectangle to be drawn
- * @param top The top side of the rectangle to be drawn
- * @param right The right side of the rectangle to be drawn
- * @param bottom The bottom side of the rectangle to be drawn
- * @param paint The paint used to draw the rect
- */
- public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) {
- super.drawRect(left, top, right, bottom, paint);
- }
-
- /**
- * Draw the specified Rect using the specified Paint. The rectangle
- * will be filled or framed based on the Style in the paint.
- *
- * @param r The rectangle to be drawn.
- * @param paint The paint used to draw the rectangle
- */
- public void drawRect(@NonNull Rect r, @NonNull Paint paint) {
- super.drawRect(r, paint);
- }
-
- /**
- * Draw the specified Rect using the specified paint. The rectangle will
- * be filled or framed based on the Style in the paint.
- *
- * @param rect The rect to be drawn
+ * @param rect The rect to be drawn
* @param paint The paint used to draw the rect
*/
public void drawRect(@NonNull RectF rect, @NonNull Paint paint) {
@@ -1621,8 +1573,33 @@
}
/**
- * Fill the entire canvas' bitmap (restricted to the current clip) with the
- * specified RGB color, using srcover porterduff mode.
+ * Draw the specified Rect using the specified Paint. The rectangle will be filled or framed
+ * based on the Style in the paint.
+ *
+ * @param r The rectangle to be drawn.
+ * @param paint The paint used to draw the rectangle
+ */
+ public void drawRect(@NonNull Rect r, @NonNull Paint paint) {
+ super.drawRect(r, paint);
+ }
+
+ /**
+ * Draw the specified Rect using the specified paint. The rectangle will be filled or framed
+ * based on the Style in the paint.
+ *
+ * @param left The left side of the rectangle to be drawn
+ * @param top The top side of the rectangle to be drawn
+ * @param right The right side of the rectangle to be drawn
+ * @param bottom The bottom side of the rectangle to be drawn
+ * @param paint The paint used to draw the rect
+ */
+ public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) {
+ super.drawRect(left, top, right, bottom, paint);
+ }
+
+ /**
+ * Fill the entire canvas' bitmap (restricted to the current clip) with the specified RGB color,
+ * using srcover porterduff mode.
*
* @param r red component (0..255) of the color to draw onto the canvas
* @param g green component (0..255) of the color to draw onto the canvas
@@ -1633,11 +1610,24 @@
}
/**
- * Draw the specified round-rect using the specified paint. The roundrect
- * will be filled or framed based on the Style in the paint.
+ * Draw the specified round-rect using the specified paint. The roundrect will be filled or
+ * framed based on the Style in the paint.
*
- * @param rx The x-radius of the oval used to round the corners
- * @param ry The y-radius of the oval used to round the corners
+ * @param rect The rectangular bounds of the roundRect to be drawn
+ * @param rx The x-radius of the oval used to round the corners
+ * @param ry The y-radius of the oval used to round the corners
+ * @param paint The paint used to draw the roundRect
+ */
+ public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
+ super.drawRoundRect(rect, rx, ry, paint);
+ }
+
+ /**
+ * Draw the specified round-rect using the specified paint. The roundrect will be filled or
+ * framed based on the Style in the paint.
+ *
+ * @param rx The x-radius of the oval used to round the corners
+ * @param ry The y-radius of the oval used to round the corners
* @param paint The paint used to draw the roundRect
*/
public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
@@ -1646,25 +1636,12 @@
}
/**
- * Draw the specified round-rect using the specified paint. The roundrect
- * will be filled or framed based on the Style in the paint.
+ * Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted
+ * based on the Align setting in the paint.
*
- * @param rect The rectangular bounds of the roundRect to be drawn
- * @param rx The x-radius of the oval used to round the corners
- * @param ry The y-radius of the oval used to round the corners
- * @param paint The paint used to draw the roundRect
- */
- public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
- super.drawRoundRect(rect, rx, ry, paint);
- }
-
- /**
- * Draw the text, with origin at (x,y), using the specified paint. The
- * origin is interpreted based on the Align setting in the paint.
- *
- * @param text The text to be drawn
- * @param x The x-coordinate of the origin of the text being drawn
- * @param y The y-coordinate of the baseline of the text being drawn
+ * @param text The text to be drawn
+ * @param x The x-coordinate of the origin of the text being drawn
+ * @param y The y-coordinate of the baseline of the text being drawn
* @param paint The paint used for the text (e.g. color, size, style)
*/
public void drawText(@NonNull char[] text, int index, int count, float x, float y,
@@ -1673,30 +1650,12 @@
}
/**
- * Draw the specified range of text, specified by start/end, with its
- * origin at (x,y), in the specified Paint. The origin is interpreted
- * based on the Align setting in the Paint.
+ * Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted
+ * based on the Align setting in the paint.
*
- * @param text The text to be drawn
- * @param start The index of the first character in text to draw
- * @param end (end - 1) is the index of the last character in text
- * to draw
- * @param x The x-coordinate of origin for where to draw the text
- * @param y The y-coordinate of origin for where to draw the text
- * @param paint The paint used for the text (e.g. color, size, style)
- */
- public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
- @NonNull Paint paint) {
- super.drawText(text, start, end, x, y, paint);
- }
-
- /**
- * Draw the text, with origin at (x,y), using the specified paint. The
- * origin is interpreted based on the Align setting in the paint.
- *
- * @param text The text to be drawn
- * @param x The x-coordinate of the origin of the text being drawn
- * @param y The y-coordinate of the baseline of the text being drawn
+ * @param text The text to be drawn
+ * @param x The x-coordinate of the origin of the text being drawn
+ * @param y The y-coordinate of the baseline of the text being drawn
* @param paint The paint used for the text (e.g. color, size, style)
*/
public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
@@ -1704,14 +1663,14 @@
}
/**
- * Draw the text, with origin at (x,y), using the specified paint.
- * The origin is interpreted based on the Align setting in the paint.
+ * Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted
+ * based on the Align setting in the paint.
*
- * @param text The text to be drawn
+ * @param text The text to be drawn
* @param start The index of the first character in text to draw
- * @param end (end - 1) is the index of the last character in text to draw
- * @param x The x-coordinate of the origin of the text being drawn
- * @param y The y-coordinate of the baseline of the text being drawn
+ * @param end (end - 1) is the index of the last character in text to draw
+ * @param x The x-coordinate of the origin of the text being drawn
+ * @param y The y-coordinate of the baseline of the text being drawn
* @param paint The paint used for the text (e.g. color, size, style)
*/
public void drawText(@NonNull String text, int start, int end, float x, float y,
@@ -1720,17 +1679,30 @@
}
/**
- * Draw the text, with origin at (x,y), using the specified paint, along
- * the specified path. The paint's Align setting determins where along the
- * path to start the text.
+ * Draw the specified range of text, specified by start/end, with its origin at (x,y), in the
+ * specified Paint. The origin is interpreted based on the Align setting in the Paint.
*
- * @param text The text to be drawn
- * @param path The path the text should follow for its baseline
- * @param hOffset The distance along the path to add to the text's
- * starting position
- * @param vOffset The distance above(-) or below(+) the path to position
- * the text
- * @param paint The paint used for the text (e.g. color, size, style)
+ * @param text The text to be drawn
+ * @param start The index of the first character in text to draw
+ * @param end (end - 1) is the index of the last character in text to draw
+ * @param x The x-coordinate of origin for where to draw the text
+ * @param y The y-coordinate of origin for where to draw the text
+ * @param paint The paint used for the text (e.g. color, size, style)
+ */
+ public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
+ @NonNull Paint paint) {
+ super.drawText(text, start, end, x, y, paint);
+ }
+
+ /**
+ * Draw the text, with origin at (x,y), using the specified paint, along the specified path. The
+ * paint's Align setting determins where along the path to start the text.
+ *
+ * @param text The text to be drawn
+ * @param path The path the text should follow for its baseline
+ * @param hOffset The distance along the path to add to the text's starting position
+ * @param vOffset The distance above(-) or below(+) the path to position the text
+ * @param paint The paint used for the text (e.g. color, size, style)
*/
public void drawTextOnPath(@NonNull char[] text, int index, int count, @NonNull Path path,
float hOffset, float vOffset, @NonNull Paint paint) {
@@ -1738,17 +1710,14 @@
}
/**
- * Draw the text, with origin at (x,y), using the specified paint, along
- * the specified path. The paint's Align setting determins where along the
- * path to start the text.
+ * Draw the text, with origin at (x,y), using the specified paint, along the specified path. The
+ * paint's Align setting determins where along the path to start the text.
*
- * @param text The text to be drawn
- * @param path The path the text should follow for its baseline
- * @param hOffset The distance along the path to add to the text's
- * starting position
- * @param vOffset The distance above(-) or below(+) the path to position
- * the text
- * @param paint The paint used for the text (e.g. color, size, style)
+ * @param text The text to be drawn
+ * @param path The path the text should follow for its baseline
+ * @param hOffset The distance along the path to add to the text's starting position
+ * @param vOffset The distance above(-) or below(+) the path to position the text
+ * @param paint The paint used for the text (e.g. color, size, style)
*/
public void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
float vOffset, @NonNull Paint paint) {
@@ -1758,21 +1727,20 @@
/**
* Draw a run of text, all in a single direction, with optional context for complex text
* shaping.
- *
- * <p>See {@link #drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}
- * for more details. This method uses a character array rather than CharSequence to
- * represent the string. Also, to be consistent with the pattern established in
- * {@link #drawText}, in this method {@code count} and {@code contextCount} are used rather
- * than offsets of the end position; {@code count = end - start, contextCount = contextEnd -
+ * <p>
+ * See {@link #drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)} for
+ * more details. This method uses a character array rather than CharSequence to represent the
+ * string. Also, to be consistent with the pattern established in {@link #drawText}, in this
+ * method {@code count} and {@code contextCount} are used rather than offsets of the end
+ * position; {@code count = end - start, contextCount = contextEnd -
* contextStart}.
*
* @param text the text to render
* @param index the start of the text to render
* @param count the count of chars to render
- * @param contextIndex the start of the context for shaping. Must be
- * no greater than index.
- * @param contextCount the number of characters in the context for shaping.
- * contexIndex + contextCount must be no less than index + count.
+ * @param contextIndex the start of the context for shaping. Must be no greater than index.
+ * @param contextCount the number of characters in the context for shaping. contexIndex +
+ * contextCount must be no less than index + count.
* @param x the x position at which to draw the text
* @param y the y position at which to draw the text
* @param isRtl whether the run is in RTL direction
@@ -1786,36 +1754,34 @@
/**
* Draw a run of text, all in a single direction, with optional context for complex text
* shaping.
- *
- * <p>The run of text includes the characters from {@code start} to {@code end} in the text. In
+ * <p>
+ * The run of text includes the characters from {@code start} to {@code end} in the text. In
* addition, the range {@code contextStart} to {@code contextEnd} is used as context for the
* purpose of complex text shaping, such as Arabic text potentially shaped differently based on
* the text next to it.
- *
- * <p>All text outside the range {@code contextStart..contextEnd} is ignored. The text between
+ * <p>
+ * All text outside the range {@code contextStart..contextEnd} is ignored. The text between
* {@code start} and {@code end} will be laid out and drawn.
- *
- * <p>The direction of the run is explicitly specified by {@code isRtl}. Thus, this method is
+ * <p>
+ * The direction of the run is explicitly specified by {@code isRtl}. Thus, this method is
* suitable only for runs of a single direction. Alignment of the text is as determined by the
* Paint's TextAlign value. Further, {@code 0 <= contextStart <= start <= end <= contextEnd
* <= text.length} must hold on entry.
- *
- * <p>Also see {@link android.graphics.Paint#getRunAdvance} for a corresponding method to
- * measure the text; the advance width of the text drawn matches the value obtained from that
- * method.
+ * <p>
+ * Also see {@link android.graphics.Paint#getRunAdvance} for a corresponding method to measure
+ * the text; the advance width of the text drawn matches the value obtained from that method.
*
* @param text the text to render
- * @param start the start of the text to render. Data before this position
- * can be used for shaping context.
- * @param end the end of the text to render. Data at or after this
- * position can be used for shaping context.
+ * @param start the start of the text to render. Data before this position can be used for
+ * shaping context.
+ * @param end the end of the text to render. Data at or after this position can be used for
+ * shaping context.
* @param contextStart the index of the start of the shaping context
* @param contextEnd the index of the end of the shaping context
* @param x the x position at which to draw the text
* @param y the y position at which to draw the text
* @param isRtl whether the run is in RTL direction
* @param paint the paint
- *
* @see #drawTextRun(char[], int, int, int, int, float, float, boolean, Paint)
*/
public void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart,
@@ -1824,32 +1790,30 @@
}
/**
- * Draw the array of vertices, interpreted as triangles (based on mode). The
- * verts array is required, and specifies the x,y pairs for each vertex. If
- * texs is non-null, then it is used to specify the coordinate in shader
- * coordinates to use at each vertex (the paint must have a shader in this
- * case). If there is no texs array, but there is a color array, then each
- * color is interpolated across its corresponding triangle in a gradient. If
- * both texs and colors arrays are present, then they behave as before, but
- * the resulting color at each pixels is the result of multiplying the
- * colors from the shader and the color-gradient together. The indices array
- * is optional, but if it is present, then it is used to specify the index
- * of each triangle, rather than just walking through the arrays in order.
+ * Draw the array of vertices, interpreted as triangles (based on mode). The verts array is
+ * required, and specifies the x,y pairs for each vertex. If texs is non-null, then it is used
+ * to specify the coordinate in shader coordinates to use at each vertex (the paint must have a
+ * shader in this case). If there is no texs array, but there is a color array, then each color
+ * is interpolated across its corresponding triangle in a gradient. If both texs and colors
+ * arrays are present, then they behave as before, but the resulting color at each pixels is the
+ * result of multiplying the colors from the shader and the color-gradient together. The indices
+ * array is optional, but if it is present, then it is used to specify the index of each
+ * triangle, rather than just walking through the arrays in order.
*
* @param mode How to interpret the array of vertices
- * @param vertexCount The number of values in the vertices array (and
- * corresponding texs and colors arrays if non-null). Each logical
- * vertex is two values (x, y), vertexCount must be a multiple of 2.
+ * @param vertexCount The number of values in the vertices array (and corresponding texs and
+ * colors arrays if non-null). Each logical vertex is two values (x, y), vertexCount
+ * must be a multiple of 2.
* @param verts Array of vertices for the mesh
* @param vertOffset Number of values in the verts to skip before drawing.
- * @param texs May be null. If not null, specifies the coordinates to sample
- * into the current shader (e.g. bitmap tile or gradient)
+ * @param texs May be null. If not null, specifies the coordinates to sample into the current
+ * shader (e.g. bitmap tile or gradient)
* @param texOffset Number of values in texs to skip before drawing.
- * @param colors May be null. If not null, specifies a color for each
- * vertex, to be interpolated across the triangle.
+ * @param colors May be null. If not null, specifies a color for each vertex, to be interpolated
+ * across the triangle.
* @param colorOffset Number of values in colors to skip before drawing.
- * @param indices If not null, array of indices to reference into the
- * vertex (texs, colors) array.
+ * @param indices If not null, array of indices to reference into the vertex (texs, colors)
+ * array.
* @param indexCount number of entries in the indices array (if not null).
* @param paint Specifies the shader to use if the texs array is non-null.
*/
diff --git a/include/androidfw/AssetManager.h b/include/androidfw/AssetManager.h
index 7aec323..4377213 100644
--- a/include/androidfw/AssetManager.h
+++ b/include/androidfw/AssetManager.h
@@ -70,11 +70,11 @@
static const char* IDMAP_BIN;
static const char* OVERLAY_DIR;
/*
- * If OVERLAY_SKU_DIR_PROPERTY is set, search for runtime resource overlay
- * APKs in OVERLAY_DIR/<value of OVERLAY_SKU_DIR_PROPERTY> in addition to
+ * If OVERLAY_THEME_DIR_PROPERTY is set, search for runtime resource overlay
+ * APKs in OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to
* OVERLAY_DIR.
*/
- static const char* OVERLAY_SKU_DIR_PROPERTY;
+ static const char* OVERLAY_THEME_DIR_PROPERTY;
static const char* TARGET_PACKAGE_NAME;
static const char* TARGET_APK_PATH;
static const char* IDMAP_DIR;
diff --git a/include/androidfw/AttributeFinder.h b/include/androidfw/AttributeFinder.h
deleted file mode 100644
index acf7056..0000000
--- a/include/androidfw/AttributeFinder.h
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef H_ATTRIBUTE_FINDER
-#define H_ATTRIBUTE_FINDER
-
-#include <stdint.h>
-#include <utils/KeyedVector.h>
-
-namespace android {
-
-static inline uint32_t getPackage(uint32_t attr) {
- return attr >> 24;
-}
-
-/**
- * A helper class to search linearly for the requested
- * attribute, maintaining it's position and optimizing for
- * the case that subsequent searches will involve an attribute with
- * a higher attribute ID.
- *
- * In the case that a subsequent attribute has a different package ID,
- * its resource ID may not be larger than the preceding search, so
- * back tracking is supported for this case. This
- * back tracking requirement is mainly for shared library
- * resources, whose package IDs get assigned at runtime
- * and thus attributes from a shared library may
- * be out of order.
- *
- * We make two assumptions about the order of attributes:
- * 1) The input has the same sorting rules applied to it as
- * the attribute data contained by this class.
- * 2) Attributes are grouped by package ID.
- * 3) Among attributes with the same package ID, the attributes are
- * sorted by increasing resource ID.
- *
- * Ex: 02010000, 02010001, 010100f4, 010100f5, 0x7f010001, 07f010003
- *
- * The total order of attributes (including package ID) can not be linear
- * as shared libraries get assigned dynamic package IDs at runtime, which
- * may break the sort order established at build time.
- */
-template <typename Derived, typename Iterator>
-class BackTrackingAttributeFinder {
-public:
- BackTrackingAttributeFinder(const Iterator& begin, const Iterator& end);
-
- Iterator find(uint32_t attr);
-
-private:
- void jumpToClosestAttribute(uint32_t packageId);
- void markCurrentPackageId(uint32_t packageId);
-
- bool mFirstTime;
- Iterator mBegin;
- Iterator mEnd;
- Iterator mCurrent;
- Iterator mLargest;
- uint32_t mLastPackageId;
- uint32_t mCurrentAttr;
-
- // Package Offsets (best-case, fast look-up).
- Iterator mFrameworkStart;
- Iterator mAppStart;
-
- // Worst case, we have shared-library resources.
- KeyedVector<uint32_t, Iterator> mPackageOffsets;
-};
-
-template <typename Derived, typename Iterator> inline
-BackTrackingAttributeFinder<Derived, Iterator>::BackTrackingAttributeFinder(const Iterator& begin, const Iterator& end)
- : mFirstTime(true)
- , mBegin(begin)
- , mEnd(end)
- , mCurrent(begin)
- , mLargest(begin)
- , mLastPackageId(0)
- , mCurrentAttr(0)
- , mFrameworkStart(end)
- , mAppStart(end) {
-}
-
-template <typename Derived, typename Iterator>
-void BackTrackingAttributeFinder<Derived, Iterator>::jumpToClosestAttribute(const uint32_t packageId) {
- switch (packageId) {
- case 0x01:
- mCurrent = mFrameworkStart;
- break;
- case 0x7f:
- mCurrent = mAppStart;
- break;
- default: {
- ssize_t idx = mPackageOffsets.indexOfKey(packageId);
- if (idx >= 0) {
- // We have seen this package ID before, so jump to the first
- // attribute with this package ID.
- mCurrent = mPackageOffsets[idx];
- } else {
- mCurrent = mEnd;
- }
- break;
- }
- }
-
- // We have never seen this package ID yet, so jump to the
- // latest/largest index we have processed so far.
- if (mCurrent == mEnd) {
- mCurrent = mLargest;
- }
-
- if (mCurrent != mEnd) {
- mCurrentAttr = static_cast<const Derived*>(this)->getAttribute(mCurrent);
- }
-}
-
-template <typename Derived, typename Iterator>
-void BackTrackingAttributeFinder<Derived, Iterator>::markCurrentPackageId(const uint32_t packageId) {
- switch (packageId) {
- case 0x01:
- mFrameworkStart = mCurrent;
- break;
- case 0x7f:
- mAppStart = mCurrent;
- break;
- default:
- mPackageOffsets.add(packageId, mCurrent);
- break;
- }
-}
-
-template <typename Derived, typename Iterator>
-Iterator BackTrackingAttributeFinder<Derived, Iterator>::find(uint32_t attr) {
- if (!(mBegin < mEnd)) {
- return mEnd;
- }
-
- if (mFirstTime) {
- // One-time initialization. We do this here instead of the constructor
- // because the derived class we access in getAttribute() may not be
- // fully constructed.
- mFirstTime = false;
- mCurrentAttr = static_cast<const Derived*>(this)->getAttribute(mBegin);
- mLastPackageId = getPackage(mCurrentAttr);
- markCurrentPackageId(mLastPackageId);
- }
-
- // Looking for the needle (attribute we're looking for)
- // in the haystack (the attributes we're searching through)
- const uint32_t needlePackageId = getPackage(attr);
- if (mLastPackageId != needlePackageId) {
- jumpToClosestAttribute(needlePackageId);
- mLastPackageId = needlePackageId;
- }
-
- // Walk through the xml attributes looking for the requested attribute.
- while (mCurrent != mEnd) {
- const uint32_t haystackPackageId = getPackage(mCurrentAttr);
- if (needlePackageId == haystackPackageId && attr < mCurrentAttr) {
- // The attribute we are looking was not found.
- break;
- }
- const uint32_t prevAttr = mCurrentAttr;
-
- // Move to the next attribute in the XML.
- ++mCurrent;
- if (mCurrent != mEnd) {
- mCurrentAttr = static_cast<const Derived*>(this)->getAttribute(mCurrent);
- const uint32_t newHaystackPackageId = getPackage(mCurrentAttr);
- if (haystackPackageId != newHaystackPackageId) {
- // We've moved to the next group of attributes
- // with a new package ID, so we should record
- // the offset of this new package ID.
- markCurrentPackageId(newHaystackPackageId);
- }
- }
-
- if (mCurrent > mLargest) {
- // We've moved past the latest attribute we've
- // seen.
- mLargest = mCurrent;
- }
-
- if (attr == prevAttr) {
- // We found the attribute we were looking for.
- return mCurrent - 1;
- }
- }
- return mEnd;
-}
-
-} // namespace android
-
-#endif // H_ATTRIBUTE_FINDER
diff --git a/include/androidfw/AttributeResolution.h b/include/androidfw/AttributeResolution.h
index 2f60a1d..3ed8bce 100644
--- a/include/androidfw/AttributeResolution.h
+++ b/include/androidfw/AttributeResolution.h
@@ -21,6 +21,18 @@
namespace android {
+// Offsets into the outValues array populated by the methods below. outValues is a uint32_t
+// array, but each logical element takes up 6 uint32_t-sized physical elements.
+enum {
+ STYLE_NUM_ENTRIES = 6,
+ STYLE_TYPE = 0,
+ STYLE_DATA = 1,
+ STYLE_ASSET_COOKIE = 2,
+ STYLE_RESOURCE_ID = 3,
+ STYLE_CHANGING_CONFIGURATIONS = 4,
+ STYLE_DENSITY = 5
+};
+
// These are all variations of the same method. They each perform the exact same operation,
// but on various data sources. I *think* they are re-written to avoid an extra branch
// in the inner loop, but after one branch miss (some pointer != null), the branch predictor should
@@ -28,26 +40,17 @@
// TODO(adamlesinski): Run performance tests against these methods and a new, single method
// that uses all the sources and branches to the right ones within the inner loop.
-bool resolveAttrs(ResTable::Theme* theme,
- uint32_t defStyleAttr,
- uint32_t defStyleRes,
- uint32_t* srcValues, size_t srcValuesLength,
- uint32_t* attrs, size_t attrsLength,
- uint32_t* outValues,
- uint32_t* outIndices);
+bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, uint32_t def_style_res,
+ uint32_t* src_values, size_t src_values_length, uint32_t* attrs,
+ size_t attrs_length, uint32_t* out_values, uint32_t* out_indices);
-bool applyStyle(ResTable::Theme* theme, ResXMLParser* xmlParser,
- uint32_t defStyleAttr,
- uint32_t defStyleRes,
- uint32_t* attrs, size_t attrsLength,
- uint32_t* outValues,
- uint32_t* outIndices);
+bool ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
+ uint32_t def_style_res, uint32_t* attrs, size_t attrs_length, uint32_t* out_values,
+ uint32_t* out_indices);
-bool retrieveAttributes(const ResTable* res, ResXMLParser* xmlParser,
- uint32_t* attrs, size_t attrsLength,
- uint32_t* outValues,
- uint32_t* outIndices);
+bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, uint32_t* attrs,
+ size_t attrs_length, uint32_t* out_values, uint32_t* out_indices);
-} // namespace android
+} // namespace android
#endif /* ANDROIDFW_ATTRIBUTERESOLUTION_H */
diff --git a/libs/androidfw/.clang-format b/libs/androidfw/.clang-format
new file mode 100644
index 0000000..ee1bee2
--- /dev/null
+++ b/libs/androidfw/.clang-format
@@ -0,0 +1,2 @@
+BasedOnStyle: Google
+ColumnLimit: 100
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 371bc9a..46fc9d4 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -73,7 +73,7 @@
const char* AssetManager::RESOURCES_FILENAME = "resources.arsc";
const char* AssetManager::IDMAP_BIN = "/system/bin/idmap";
const char* AssetManager::OVERLAY_DIR = "/vendor/overlay";
-const char* AssetManager::OVERLAY_SKU_DIR_PROPERTY = "ro.boot.vendor.overlay.sku";
+const char* AssetManager::OVERLAY_THEME_DIR_PROPERTY = "ro.boot.vendor.overlay.theme";
const char* AssetManager::TARGET_PACKAGE_NAME = "android";
const char* AssetManager::TARGET_APK_PATH = "/system/framework/framework-res.apk";
const char* AssetManager::IDMAP_DIR = "/data/resource-cache";
diff --git a/libs/androidfw/AttributeFinder.h b/libs/androidfw/AttributeFinder.h
new file mode 100644
index 0000000..f281921
--- /dev/null
+++ b/libs/androidfw/AttributeFinder.h
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef ANDROIDFW_ATTRIBUTE_FINDER_H
+#define ANDROIDFW_ATTRIBUTE_FINDER_H
+
+#include <utils/KeyedVector.h>
+
+#include <stdint.h>
+
+namespace android {
+
+static inline uint32_t get_package(uint32_t attr) { return attr >> 24; }
+
+/**
+ * A helper class to search linearly for the requested
+ * attribute, maintaining it's position and optimizing for
+ * the case that subsequent searches will involve an attribute with
+ * a higher attribute ID.
+ *
+ * In the case that a subsequent attribute has a different package ID,
+ * its resource ID may not be larger than the preceding search, so
+ * back tracking is supported for this case. This
+ * back tracking requirement is mainly for shared library
+ * resources, whose package IDs get assigned at runtime
+ * and thus attributes from a shared library may
+ * be out of order.
+ *
+ * We make two assumptions about the order of attributes:
+ * 1) The input has the same sorting rules applied to it as
+ * the attribute data contained by this class.
+ * 2) Attributes are grouped by package ID.
+ * 3) Among attributes with the same package ID, the attributes are
+ * sorted by increasing resource ID.
+ *
+ * Ex: 02010000, 02010001, 010100f4, 010100f5, 0x7f010001, 07f010003
+ *
+ * The total order of attributes (including package ID) can not be linear
+ * as shared libraries get assigned dynamic package IDs at runtime, which
+ * may break the sort order established at build time.
+ */
+template <typename Derived, typename Iterator>
+class BackTrackingAttributeFinder {
+ public:
+ BackTrackingAttributeFinder(const Iterator& begin, const Iterator& end);
+
+ Iterator Find(uint32_t attr);
+
+ private:
+ void JumpToClosestAttribute(uint32_t package_id);
+ void MarkCurrentPackageId(uint32_t package_id);
+
+ bool first_time_;
+ Iterator begin_;
+ Iterator end_;
+ Iterator current_;
+ Iterator largest_;
+ uint32_t last_package_id_;
+ uint32_t current_attr_;
+
+ // Package offsets (best-case, fast look-up).
+ Iterator framework_start_;
+ Iterator app_start_;
+
+ // Worst case, we have shared-library resources.
+ KeyedVector<uint32_t, Iterator> package_offsets_;
+};
+
+template <typename Derived, typename Iterator>
+inline BackTrackingAttributeFinder<Derived, Iterator>::BackTrackingAttributeFinder(
+ const Iterator& begin, const Iterator& end)
+ : first_time_(true),
+ begin_(begin),
+ end_(end),
+ current_(begin),
+ largest_(begin),
+ last_package_id_(0),
+ current_attr_(0),
+ framework_start_(end),
+ app_start_(end) {}
+
+template <typename Derived, typename Iterator>
+void BackTrackingAttributeFinder<Derived, Iterator>::JumpToClosestAttribute(
+ const uint32_t package_id) {
+ switch (package_id) {
+ case 0x01u:
+ current_ = framework_start_;
+ break;
+ case 0x7fu:
+ current_ = app_start_;
+ break;
+ default: {
+ ssize_t idx = package_offsets_.indexOfKey(package_id);
+ if (idx >= 0) {
+ // We have seen this package ID before, so jump to the first
+ // attribute with this package ID.
+ current_ = package_offsets_[idx];
+ } else {
+ current_ = end_;
+ }
+ break;
+ }
+ }
+
+ // We have never seen this package ID yet, so jump to the
+ // latest/largest index we have processed so far.
+ if (current_ == end_) {
+ current_ = largest_;
+ }
+
+ if (current_ != end_) {
+ current_attr_ = static_cast<const Derived*>(this)->GetAttribute(current_);
+ }
+}
+
+template <typename Derived, typename Iterator>
+void BackTrackingAttributeFinder<Derived, Iterator>::MarkCurrentPackageId(
+ const uint32_t package_id) {
+ switch (package_id) {
+ case 0x01u:
+ framework_start_ = current_;
+ break;
+ case 0x7fu:
+ app_start_ = current_;
+ break;
+ default:
+ package_offsets_.add(package_id, current_);
+ break;
+ }
+}
+
+template <typename Derived, typename Iterator>
+Iterator BackTrackingAttributeFinder<Derived, Iterator>::Find(uint32_t attr) {
+ if (!(begin_ < end_)) {
+ return end_;
+ }
+
+ if (first_time_) {
+ // One-time initialization. We do this here instead of the constructor
+ // because the derived class we access in getAttribute() may not be
+ // fully constructed.
+ first_time_ = false;
+ current_attr_ = static_cast<const Derived*>(this)->GetAttribute(begin_);
+ last_package_id_ = get_package(current_attr_);
+ MarkCurrentPackageId(last_package_id_);
+ }
+
+ // Looking for the needle (attribute we're looking for)
+ // in the haystack (the attributes we're searching through)
+ const uint32_t needle_package_id = get_package(attr);
+ if (last_package_id_ != needle_package_id) {
+ JumpToClosestAttribute(needle_package_id);
+ last_package_id_ = needle_package_id;
+ }
+
+ // Walk through the xml attributes looking for the requested attribute.
+ while (current_ != end_) {
+ const uint32_t haystack_package_id = get_package(current_attr_);
+ if (needle_package_id == haystack_package_id && attr < current_attr_) {
+ // The attribute we are looking was not found.
+ break;
+ }
+ const uint32_t prev_attr = current_attr_;
+
+ // Move to the next attribute in the XML.
+ ++current_;
+ if (current_ != end_) {
+ current_attr_ = static_cast<const Derived*>(this)->GetAttribute(current_);
+ const uint32_t new_haystack_package_id = get_package(current_attr_);
+ if (haystack_package_id != new_haystack_package_id) {
+ // We've moved to the next group of attributes
+ // with a new package ID, so we should record
+ // the offset of this new package ID.
+ MarkCurrentPackageId(new_haystack_package_id);
+ }
+ }
+
+ if (current_ > largest_) {
+ // We've moved past the latest attribute we've seen.
+ largest_ = current_;
+ }
+
+ if (attr == prev_attr) {
+ // We found the attribute we were looking for.
+ return current_ - 1;
+ }
+ }
+ return end_;
+}
+
+} // namespace android
+
+#endif // ANDROIDFW_ATTRIBUTE_FINDER_H
diff --git a/libs/androidfw/AttributeResolution.cpp b/libs/androidfw/AttributeResolution.cpp
index ad428a4..00f7a42 100644
--- a/libs/androidfw/AttributeResolution.cpp
+++ b/libs/androidfw/AttributeResolution.cpp
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-#include "androidfw/AttributeFinder.h"
+#include "AttributeFinder.h"
+
#include "androidfw/AttributeResolution.h"
#include "androidfw/ResourceTypes.h"
@@ -25,476 +26,452 @@
namespace android {
-enum {
- STYLE_NUM_ENTRIES = 6,
- STYLE_TYPE = 0,
- STYLE_DATA = 1,
- STYLE_ASSET_COOKIE = 2,
- STYLE_RESOURCE_ID = 3,
- STYLE_CHANGING_CONFIGURATIONS = 4,
- STYLE_DENSITY = 5
-};
-
class XmlAttributeFinder : public BackTrackingAttributeFinder<XmlAttributeFinder, size_t> {
-public:
- explicit XmlAttributeFinder(const ResXMLParser* parser) :
- BackTrackingAttributeFinder(0, parser != NULL ? parser->getAttributeCount() : 0),
- mParser(parser) {
- }
+ public:
+ explicit XmlAttributeFinder(const ResXMLParser* parser)
+ : BackTrackingAttributeFinder(0, parser != NULL ? parser->getAttributeCount() : 0),
+ parser_(parser) {}
- inline uint32_t getAttribute(size_t index) const {
- return mParser->getAttributeNameResID(index);
- }
+ inline uint32_t GetAttribute(size_t index) const { return parser_->getAttributeNameResID(index); }
-private:
- const ResXMLParser* mParser;
+ private:
+ const ResXMLParser* parser_;
};
-class BagAttributeFinder :
- public BackTrackingAttributeFinder<BagAttributeFinder, const ResTable::bag_entry*> {
-public:
- BagAttributeFinder(const ResTable::bag_entry* start, const ResTable::bag_entry* end) :
- BackTrackingAttributeFinder(start, end) {}
+class BagAttributeFinder
+ : public BackTrackingAttributeFinder<BagAttributeFinder, const ResTable::bag_entry*> {
+ public:
+ BagAttributeFinder(const ResTable::bag_entry* start, const ResTable::bag_entry* end)
+ : BackTrackingAttributeFinder(start, end) {}
- inline uint32_t getAttribute(const ResTable::bag_entry* entry) const {
- return entry->map.name.ident;
- }
+ inline uint32_t GetAttribute(const ResTable::bag_entry* entry) const {
+ return entry->map.name.ident;
+ }
};
-bool resolveAttrs(ResTable::Theme* theme,
- uint32_t defStyleAttr,
- uint32_t defStyleRes,
- uint32_t* srcValues, size_t srcValuesLength,
- uint32_t* attrs, size_t attrsLength,
- uint32_t* outValues,
- uint32_t* outIndices) {
+bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, uint32_t def_style_res,
+ uint32_t* src_values, size_t src_values_length, uint32_t* attrs,
+ size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) {
+ if (kDebugStyles) {
+ ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x", theme, def_style_attr,
+ def_style_res);
+ }
+
+ const ResTable& res = theme->getResTable();
+ ResTable_config config;
+ Res_value value;
+
+ int indices_idx = 0;
+
+ // Load default style from attribute, if specified...
+ uint32_t def_style_bag_type_set_flags = 0;
+ if (def_style_attr != 0) {
+ Res_value value;
+ if (theme->getAttribute(def_style_attr, &value, &def_style_bag_type_set_flags) >= 0) {
+ if (value.dataType == Res_value::TYPE_REFERENCE) {
+ def_style_res = value.data;
+ }
+ }
+ }
+
+ // Now lock down the resource object and start pulling stuff from it.
+ res.lock();
+
+ // Retrieve the default style bag, if requested.
+ const ResTable::bag_entry* def_style_start = NULL;
+ uint32_t def_style_type_set_flags = 0;
+ ssize_t bag_off =
+ def_style_res != 0
+ ? res.getBagLocked(def_style_res, &def_style_start, &def_style_type_set_flags)
+ : -1;
+ def_style_type_set_flags |= def_style_bag_type_set_flags;
+ const ResTable::bag_entry* const def_style_end = def_style_start + (bag_off >= 0 ? bag_off : 0);
+ BagAttributeFinder def_style_attr_finder(def_style_start, def_style_end);
+
+ // Now iterate through all of the attributes that the client has requested,
+ // filling in each with whatever data we can find.
+ for (size_t ii = 0; ii < attrs_length; ii++) {
+ const uint32_t cur_ident = attrs[ii];
+
if (kDebugStyles) {
- ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x "
- "defStyleRes=0x%x", theme, defStyleAttr, defStyleRes);
+ ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident);
}
- const ResTable& res = theme->getResTable();
- ResTable_config config;
- Res_value value;
+ ssize_t block = -1;
+ uint32_t type_set_flags = 0;
- int indicesIdx = 0;
+ value.dataType = Res_value::TYPE_NULL;
+ value.data = Res_value::DATA_NULL_UNDEFINED;
+ config.density = 0;
- // Load default style from attribute, if specified...
- uint32_t defStyleBagTypeSetFlags = 0;
- if (defStyleAttr != 0) {
- Res_value value;
- if (theme->getAttribute(defStyleAttr, &value, &defStyleBagTypeSetFlags) >= 0) {
- if (value.dataType == Res_value::TYPE_REFERENCE) {
- defStyleRes = value.data;
- }
- }
+ // Try to find a value for this attribute... we prioritize values
+ // coming from, first XML attributes, then XML style, then default
+ // style, and finally the theme.
+
+ // Retrieve the current input value if available.
+ if (src_values_length > 0 && src_values[ii] != 0) {
+ value.dataType = Res_value::TYPE_ATTRIBUTE;
+ value.data = src_values[ii];
+ if (kDebugStyles) {
+ ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data);
+ }
}
- // Now lock down the resource object and start pulling stuff from it.
- res.lock();
-
- // Retrieve the default style bag, if requested.
- const ResTable::bag_entry* defStyleStart = NULL;
- uint32_t defStyleTypeSetFlags = 0;
- ssize_t bagOff = defStyleRes != 0
- ? res.getBagLocked(defStyleRes, &defStyleStart, &defStyleTypeSetFlags) : -1;
- defStyleTypeSetFlags |= defStyleBagTypeSetFlags;
- const ResTable::bag_entry* const defStyleEnd = defStyleStart + (bagOff >= 0 ? bagOff : 0);
- BagAttributeFinder defStyleAttrFinder(defStyleStart, defStyleEnd);
-
- // Now iterate through all of the attributes that the client has requested,
- // filling in each with whatever data we can find.
- ssize_t block = 0;
- uint32_t typeSetFlags;
- for (size_t ii=0; ii<attrsLength; ii++) {
- const uint32_t curIdent = attrs[ii];
-
+ if (value.dataType == Res_value::TYPE_NULL) {
+ const ResTable::bag_entry* const def_style_entry = def_style_attr_finder.Find(cur_ident);
+ if (def_style_entry != def_style_end) {
+ block = def_style_entry->stringBlock;
+ type_set_flags = def_style_type_set_flags;
+ value = def_style_entry->map.value;
if (kDebugStyles) {
- ALOGI("RETRIEVING ATTR 0x%08x...", curIdent);
+ ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
}
+ }
+ }
- // Try to find a value for this attribute... we prioritize values
- // coming from, first XML attributes, then XML style, then default
- // style, and finally the theme.
- value.dataType = Res_value::TYPE_NULL;
- value.data = Res_value::DATA_NULL_UNDEFINED;
- typeSetFlags = 0;
- config.density = 0;
-
- // Retrieve the current input value if available.
- if (srcValuesLength > 0 && srcValues[ii] != 0) {
- block = -1;
- value.dataType = Res_value::TYPE_ATTRIBUTE;
- value.data = srcValues[ii];
- if (kDebugStyles) {
- ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data);
- }
- }
-
- if (value.dataType == Res_value::TYPE_NULL) {
- const ResTable::bag_entry* const defStyleEntry = defStyleAttrFinder.find(curIdent);
- if (defStyleEntry != defStyleEnd) {
- block = defStyleEntry->stringBlock;
- typeSetFlags = defStyleTypeSetFlags;
- value = defStyleEntry->map.value;
- if (kDebugStyles) {
- ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
- }
- }
- }
-
- uint32_t resid = 0;
- if (value.dataType != Res_value::TYPE_NULL) {
- // Take care of resolving the found resource to its final value.
- ssize_t newBlock = theme->resolveAttributeReference(&value, block,
- &resid, &typeSetFlags, &config);
- if (newBlock >= 0) block = newBlock;
- if (kDebugStyles) {
- ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data);
- }
- } else {
- // If we still don't have a value for this attribute, try to find
- // it in the theme!
- ssize_t newBlock = theme->getAttribute(curIdent, &value, &typeSetFlags);
- if (newBlock >= 0) {
- if (kDebugStyles) {
- ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
- }
- newBlock = res.resolveReference(&value, block, &resid,
- &typeSetFlags, &config);
- if (newBlock >= 0) block = newBlock;
- if (kDebugStyles) {
- ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data);
- }
- }
- }
-
- // Deal with the special @null value -- it turns back to TYPE_NULL.
- if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
- if (kDebugStyles) {
- ALOGI("-> Setting to @null!");
- }
- value.dataType = Res_value::TYPE_NULL;
- value.data = Res_value::DATA_NULL_UNDEFINED;
- block = -1;
- }
-
+ uint32_t resid = 0;
+ if (value.dataType != Res_value::TYPE_NULL) {
+ // Take care of resolving the found resource to its final value.
+ ssize_t new_block =
+ theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config);
+ if (new_block >= 0) block = new_block;
+ if (kDebugStyles) {
+ ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data);
+ }
+ } else {
+ // If we still don't have a value for this attribute, try to find
+ // it in the theme!
+ ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags);
+ if (new_block >= 0) {
if (kDebugStyles) {
- ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", curIdent, value.dataType,
- value.data);
+ ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
-
- // Write the final value back to Java.
- outValues[STYLE_TYPE] = value.dataType;
- outValues[STYLE_DATA] = value.data;
- outValues[STYLE_ASSET_COOKIE] = block != -1
- ? static_cast<uint32_t>(res.getTableCookie(block)) : static_cast<uint32_t>(-1);
- outValues[STYLE_RESOURCE_ID] = resid;
- outValues[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
- outValues[STYLE_DENSITY] = config.density;
-
- if (outIndices != NULL && value.dataType != Res_value::TYPE_NULL) {
- indicesIdx++;
- outIndices[indicesIdx] = ii;
+ new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config);
+ if (new_block >= 0) block = new_block;
+ if (kDebugStyles) {
+ ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
-
- outValues += STYLE_NUM_ENTRIES;
+ }
}
- res.unlock();
-
- if (outIndices != NULL) {
- outIndices[0] = indicesIdx;
+ // Deal with the special @null value -- it turns back to TYPE_NULL.
+ if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
+ if (kDebugStyles) {
+ ALOGI("-> Setting to @null!");
+ }
+ value.dataType = Res_value::TYPE_NULL;
+ value.data = Res_value::DATA_NULL_UNDEFINED;
+ block = -1;
}
- return true;
-}
-bool applyStyle(ResTable::Theme* theme, ResXMLParser* xmlParser,
- uint32_t defStyleAttr,
- uint32_t defStyleRes,
- uint32_t* attrs, size_t attrsLength,
- uint32_t* outValues,
- uint32_t* outIndices) {
if (kDebugStyles) {
- ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p",
- theme, defStyleAttr, defStyleRes, xmlParser);
+ ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident, value.dataType, value.data);
}
- const ResTable& res = theme->getResTable();
- ResTable_config config;
+ // Write the final value back to Java.
+ out_values[STYLE_TYPE] = value.dataType;
+ out_values[STYLE_DATA] = value.data;
+ out_values[STYLE_ASSET_COOKIE] =
+ block != -1 ? static_cast<uint32_t>(res.getTableCookie(block)) : static_cast<uint32_t>(-1);
+ out_values[STYLE_RESOURCE_ID] = resid;
+ out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
+ out_values[STYLE_DENSITY] = config.density;
+
+ if (out_indices != NULL && value.dataType != Res_value::TYPE_NULL) {
+ indices_idx++;
+ out_indices[indices_idx] = ii;
+ }
+
+ out_values += STYLE_NUM_ENTRIES;
+ }
+
+ res.unlock();
+
+ if (out_indices != NULL) {
+ out_indices[0] = indices_idx;
+ }
+ return true;
+}
+
+bool ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
+ uint32_t def_style_res, uint32_t* attrs, size_t attrs_length, uint32_t* out_values,
+ uint32_t* out_indices) {
+ if (kDebugStyles) {
+ ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", theme,
+ def_style_attr, def_style_res, xml_parser);
+ }
+
+ const ResTable& res = theme->getResTable();
+ ResTable_config config;
+ Res_value value;
+
+ int indices_idx = 0;
+
+ // Load default style from attribute, if specified...
+ uint32_t def_style_bag_type_set_flags = 0;
+ if (def_style_attr != 0) {
Res_value value;
+ if (theme->getAttribute(def_style_attr, &value, &def_style_bag_type_set_flags) >= 0) {
+ if (value.dataType == Res_value::TYPE_REFERENCE) {
+ def_style_res = value.data;
+ }
+ }
+ }
- int indicesIdx = 0;
-
- // Load default style from attribute, if specified...
- uint32_t defStyleBagTypeSetFlags = 0;
- if (defStyleAttr != 0) {
- Res_value value;
- if (theme->getAttribute(defStyleAttr, &value, &defStyleBagTypeSetFlags) >= 0) {
- if (value.dataType == Res_value::TYPE_REFERENCE) {
- defStyleRes = value.data;
- }
+ // Retrieve the style class associated with the current XML tag.
+ int style = 0;
+ uint32_t style_bag_type_set_flags = 0;
+ if (xml_parser != NULL) {
+ ssize_t idx = xml_parser->indexOfStyle();
+ if (idx >= 0 && xml_parser->getAttributeValue(idx, &value) >= 0) {
+ if (value.dataType == value.TYPE_ATTRIBUTE) {
+ if (theme->getAttribute(value.data, &value, &style_bag_type_set_flags) < 0) {
+ value.dataType = Res_value::TYPE_NULL;
}
+ }
+ if (value.dataType == value.TYPE_REFERENCE) {
+ style = value.data;
+ }
+ }
+ }
+
+ // Now lock down the resource object and start pulling stuff from it.
+ res.lock();
+
+ // Retrieve the default style bag, if requested.
+ const ResTable::bag_entry* def_style_attr_start = NULL;
+ uint32_t def_style_type_set_flags = 0;
+ ssize_t bag_off =
+ def_style_res != 0
+ ? res.getBagLocked(def_style_res, &def_style_attr_start, &def_style_type_set_flags)
+ : -1;
+ def_style_type_set_flags |= def_style_bag_type_set_flags;
+ const ResTable::bag_entry* const def_style_attr_end =
+ def_style_attr_start + (bag_off >= 0 ? bag_off : 0);
+ BagAttributeFinder def_style_attr_finder(def_style_attr_start, def_style_attr_end);
+
+ // Retrieve the style class bag, if requested.
+ const ResTable::bag_entry* style_attr_start = NULL;
+ uint32_t style_type_set_flags = 0;
+ bag_off = style != 0 ? res.getBagLocked(style, &style_attr_start, &style_type_set_flags) : -1;
+ style_type_set_flags |= style_bag_type_set_flags;
+ const ResTable::bag_entry* const style_attr_end = style_attr_start + (bag_off >= 0 ? bag_off : 0);
+ BagAttributeFinder style_attr_finder(style_attr_start, style_attr_end);
+
+ // Retrieve the XML attributes, if requested.
+ static const ssize_t kXmlBlock = 0x10000000;
+ XmlAttributeFinder xml_attr_finder(xml_parser);
+ const size_t xml_attr_end = xml_parser != NULL ? xml_parser->getAttributeCount() : 0;
+
+ // Now iterate through all of the attributes that the client has requested,
+ // filling in each with whatever data we can find.
+ for (size_t ii = 0; ii < attrs_length; ii++) {
+ const uint32_t cur_ident = attrs[ii];
+
+ if (kDebugStyles) {
+ ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident);
}
- // Retrieve the style class associated with the current XML tag.
- int style = 0;
- uint32_t styleBagTypeSetFlags = 0;
- if (xmlParser != NULL) {
- ssize_t idx = xmlParser->indexOfStyle();
- if (idx >= 0 && xmlParser->getAttributeValue(idx, &value) >= 0) {
- if (value.dataType == value.TYPE_ATTRIBUTE) {
- if (theme->getAttribute(value.data, &value, &styleBagTypeSetFlags) < 0) {
- value.dataType = Res_value::TYPE_NULL;
- }
- }
- if (value.dataType == value.TYPE_REFERENCE) {
- style = value.data;
- }
- }
+ ssize_t block = kXmlBlock;
+ uint32_t type_set_flags = 0;
+
+ value.dataType = Res_value::TYPE_NULL;
+ value.data = Res_value::DATA_NULL_UNDEFINED;
+ config.density = 0;
+
+ // Try to find a value for this attribute... we prioritize values
+ // coming from, first XML attributes, then XML style, then default
+ // style, and finally the theme.
+
+ // Walk through the xml attributes looking for the requested attribute.
+ const size_t xml_attr_idx = xml_attr_finder.Find(cur_ident);
+ if (xml_attr_idx != xml_attr_end) {
+ // We found the attribute we were looking for.
+ xml_parser->getAttributeValue(xml_attr_idx, &value);
+ if (kDebugStyles) {
+ ALOGI("-> From XML: type=0x%x, data=0x%08x", value.dataType, value.data);
+ }
}
- // Now lock down the resource object and start pulling stuff from it.
- res.lock();
-
- // Retrieve the default style bag, if requested.
- const ResTable::bag_entry* defStyleAttrStart = NULL;
- uint32_t defStyleTypeSetFlags = 0;
- ssize_t bagOff = defStyleRes != 0
- ? res.getBagLocked(defStyleRes, &defStyleAttrStart, &defStyleTypeSetFlags) : -1;
- defStyleTypeSetFlags |= defStyleBagTypeSetFlags;
- const ResTable::bag_entry* const defStyleAttrEnd = defStyleAttrStart + (bagOff >= 0 ? bagOff : 0);
- BagAttributeFinder defStyleAttrFinder(defStyleAttrStart, defStyleAttrEnd);
-
- // Retrieve the style class bag, if requested.
- const ResTable::bag_entry* styleAttrStart = NULL;
- uint32_t styleTypeSetFlags = 0;
- bagOff = style != 0 ? res.getBagLocked(style, &styleAttrStart, &styleTypeSetFlags) : -1;
- styleTypeSetFlags |= styleBagTypeSetFlags;
- const ResTable::bag_entry* const styleAttrEnd = styleAttrStart + (bagOff >= 0 ? bagOff : 0);
- BagAttributeFinder styleAttrFinder(styleAttrStart, styleAttrEnd);
-
- // Retrieve the XML attributes, if requested.
- static const ssize_t kXmlBlock = 0x10000000;
- XmlAttributeFinder xmlAttrFinder(xmlParser);
- const size_t xmlAttrEnd = xmlParser != NULL ? xmlParser->getAttributeCount() : 0;
-
- // Now iterate through all of the attributes that the client has requested,
- // filling in each with whatever data we can find.
- ssize_t block = 0;
- uint32_t typeSetFlags;
- for (size_t ii = 0; ii < attrsLength; ii++) {
- const uint32_t curIdent = attrs[ii];
-
+ if (value.dataType == Res_value::TYPE_NULL) {
+ // Walk through the style class values looking for the requested attribute.
+ const ResTable::bag_entry* const style_attr_entry = style_attr_finder.Find(cur_ident);
+ if (style_attr_entry != style_attr_end) {
+ // We found the attribute we were looking for.
+ block = style_attr_entry->stringBlock;
+ type_set_flags = style_type_set_flags;
+ value = style_attr_entry->map.value;
if (kDebugStyles) {
- ALOGI("RETRIEVING ATTR 0x%08x...", curIdent);
+ ALOGI("-> From style: type=0x%x, data=0x%08x", value.dataType, value.data);
}
+ }
+ }
- // Try to find a value for this attribute... we prioritize values
- // coming from, first XML attributes, then XML style, then default
- // style, and finally the theme.
- value.dataType = Res_value::TYPE_NULL;
- value.data = Res_value::DATA_NULL_UNDEFINED;
- typeSetFlags = 0;
- config.density = 0;
-
- // Walk through the xml attributes looking for the requested attribute.
- const size_t xmlAttrIdx = xmlAttrFinder.find(curIdent);
- if (xmlAttrIdx != xmlAttrEnd) {
- // We found the attribute we were looking for.
- block = kXmlBlock;
- xmlParser->getAttributeValue(xmlAttrIdx, &value);
- if (kDebugStyles) {
- ALOGI("-> From XML: type=0x%x, data=0x%08x", value.dataType, value.data);
- }
+ if (value.dataType == Res_value::TYPE_NULL) {
+ // Walk through the default style values looking for the requested attribute.
+ const ResTable::bag_entry* const def_style_attr_entry = def_style_attr_finder.Find(cur_ident);
+ if (def_style_attr_entry != def_style_attr_end) {
+ // We found the attribute we were looking for.
+ block = def_style_attr_entry->stringBlock;
+ type_set_flags = style_type_set_flags;
+ value = def_style_attr_entry->map.value;
+ if (kDebugStyles) {
+ ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
}
+ }
+ }
- if (value.dataType == Res_value::TYPE_NULL) {
- // Walk through the style class values looking for the requested attribute.
- const ResTable::bag_entry* const styleAttrEntry = styleAttrFinder.find(curIdent);
- if (styleAttrEntry != styleAttrEnd) {
- // We found the attribute we were looking for.
- block = styleAttrEntry->stringBlock;
- typeSetFlags = styleTypeSetFlags;
- value = styleAttrEntry->map.value;
- if (kDebugStyles) {
- ALOGI("-> From style: type=0x%x, data=0x%08x", value.dataType, value.data);
- }
- }
+ uint32_t resid = 0;
+ if (value.dataType != Res_value::TYPE_NULL) {
+ // Take care of resolving the found resource to its final value.
+ ssize_t new_block =
+ theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config);
+ if (new_block >= 0) {
+ block = new_block;
+ }
+
+ if (kDebugStyles) {
+ ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data);
+ }
+ } else {
+ // If we still don't have a value for this attribute, try to find
+ // it in the theme!
+ ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags);
+ if (new_block >= 0) {
+ if (kDebugStyles) {
+ ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
-
- if (value.dataType == Res_value::TYPE_NULL) {
- // Walk through the default style values looking for the requested attribute.
- const ResTable::bag_entry* const defStyleAttrEntry = defStyleAttrFinder.find(curIdent);
- if (defStyleAttrEntry != defStyleAttrEnd) {
- // We found the attribute we were looking for.
- block = defStyleAttrEntry->stringBlock;
- typeSetFlags = styleTypeSetFlags;
- value = defStyleAttrEntry->map.value;
- if (kDebugStyles) {
- ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
- }
- }
- }
-
- uint32_t resid = 0;
- if (value.dataType != Res_value::TYPE_NULL) {
- // Take care of resolving the found resource to its final value.
- ssize_t newBlock = theme->resolveAttributeReference(&value, block,
- &resid, &typeSetFlags, &config);
- if (newBlock >= 0) {
- block = newBlock;
- }
-
- if (kDebugStyles) {
- ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data);
- }
- } else {
- // If we still don't have a value for this attribute, try to find
- // it in the theme!
- ssize_t newBlock = theme->getAttribute(curIdent, &value, &typeSetFlags);
- if (newBlock >= 0) {
- if (kDebugStyles) {
- ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
- }
- newBlock = res.resolveReference(&value, block, &resid,
- &typeSetFlags, &config);
-
- if (newBlock >= 0) {
- block = newBlock;
- }
-
- if (kDebugStyles) {
- ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data);
- }
- }
- }
-
- // Deal with the special @null value -- it turns back to TYPE_NULL.
- if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
- if (kDebugStyles) {
- ALOGI("-> Setting to @null!");
- }
- value.dataType = Res_value::TYPE_NULL;
- value.data = Res_value::DATA_NULL_UNDEFINED;
- block = kXmlBlock;
+ new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config);
+ if (new_block >= 0) {
+ block = new_block;
}
if (kDebugStyles) {
- ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", curIdent, value.dataType, value.data);
+ ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
-
- // Write the final value back to Java.
- outValues[STYLE_TYPE] = value.dataType;
- outValues[STYLE_DATA] = value.data;
- outValues[STYLE_ASSET_COOKIE] = block != kXmlBlock ?
- static_cast<uint32_t>(res.getTableCookie(block)) : static_cast<uint32_t>(-1);
- outValues[STYLE_RESOURCE_ID] = resid;
- outValues[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
- outValues[STYLE_DENSITY] = config.density;
-
- if (outIndices != NULL && value.dataType != Res_value::TYPE_NULL) {
- indicesIdx++;
- outIndices[indicesIdx] = ii;
- }
-
- outValues += STYLE_NUM_ENTRIES;
+ }
}
- res.unlock();
-
- if (outIndices != NULL) {
- outIndices[0] = indicesIdx;
+ // Deal with the special @null value -- it turns back to TYPE_NULL.
+ if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
+ if (kDebugStyles) {
+ ALOGI("-> Setting to @null!");
+ }
+ value.dataType = Res_value::TYPE_NULL;
+ value.data = Res_value::DATA_NULL_UNDEFINED;
+ block = kXmlBlock;
}
- return true;
+
+ if (kDebugStyles) {
+ ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident, value.dataType, value.data);
+ }
+
+ // Write the final value back to Java.
+ out_values[STYLE_TYPE] = value.dataType;
+ out_values[STYLE_DATA] = value.data;
+ out_values[STYLE_ASSET_COOKIE] = block != kXmlBlock
+ ? static_cast<uint32_t>(res.getTableCookie(block))
+ : static_cast<uint32_t>(-1);
+ out_values[STYLE_RESOURCE_ID] = resid;
+ out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
+ out_values[STYLE_DENSITY] = config.density;
+
+ if (out_indices != NULL && value.dataType != Res_value::TYPE_NULL) {
+ indices_idx++;
+ out_indices[indices_idx] = ii;
+ }
+
+ out_values += STYLE_NUM_ENTRIES;
+ }
+
+ res.unlock();
+
+ if (out_indices != NULL) {
+ out_indices[0] = indices_idx;
+ }
+ return true;
}
-bool retrieveAttributes(const ResTable* res, ResXMLParser* xmlParser,
- uint32_t* attrs, size_t attrsLength,
- uint32_t* outValues,
- uint32_t* outIndices) {
- ResTable_config config;
- Res_value value;
+bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, uint32_t* attrs,
+ size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) {
+ ResTable_config config;
+ Res_value value;
- int indicesIdx = 0;
+ int indices_idx = 0;
- // Now lock down the resource object and start pulling stuff from it.
- res->lock();
+ // Now lock down the resource object and start pulling stuff from it.
+ res->lock();
- // Retrieve the XML attributes, if requested.
- const size_t NX = xmlParser->getAttributeCount();
- size_t ix=0;
- uint32_t curXmlAttr = xmlParser->getAttributeNameResID(ix);
+ // Retrieve the XML attributes, if requested.
+ const size_t xml_attr_count = xml_parser->getAttributeCount();
+ size_t ix = 0;
+ uint32_t cur_xml_attr = xml_parser->getAttributeNameResID(ix);
- static const ssize_t kXmlBlock = 0x10000000;
+ static const ssize_t kXmlBlock = 0x10000000;
- // Now iterate through all of the attributes that the client has requested,
- // filling in each with whatever data we can find.
- ssize_t block = 0;
- uint32_t typeSetFlags;
- for (size_t ii=0; ii<attrsLength; ii++) {
- const uint32_t curIdent = attrs[ii];
+ // Now iterate through all of the attributes that the client has requested,
+ // filling in each with whatever data we can find.
+ for (size_t ii = 0; ii < attrs_length; ii++) {
+ const uint32_t cur_ident = attrs[ii];
+ ssize_t block = kXmlBlock;
+ uint32_t type_set_flags = 0;
- // Try to find a value for this attribute...
- value.dataType = Res_value::TYPE_NULL;
- value.data = Res_value::DATA_NULL_UNDEFINED;
- typeSetFlags = 0;
- config.density = 0;
+ value.dataType = Res_value::TYPE_NULL;
+ value.data = Res_value::DATA_NULL_UNDEFINED;
+ config.density = 0;
- // Skip through XML attributes until the end or the next possible match.
- while (ix < NX && curIdent > curXmlAttr) {
- ix++;
- curXmlAttr = xmlParser->getAttributeNameResID(ix);
- }
- // Retrieve the current XML attribute if it matches, and step to next.
- if (ix < NX && curIdent == curXmlAttr) {
- block = kXmlBlock;
- xmlParser->getAttributeValue(ix, &value);
- ix++;
- curXmlAttr = xmlParser->getAttributeNameResID(ix);
- }
-
- //printf("Attribute 0x%08x: type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
- uint32_t resid = 0;
- if (value.dataType != Res_value::TYPE_NULL) {
- // Take care of resolving the found resource to its final value.
- //printf("Resolving attribute reference\n");
- ssize_t newBlock = res->resolveReference(&value, block, &resid,
- &typeSetFlags, &config);
- if (newBlock >= 0) block = newBlock;
- }
-
- // Deal with the special @null value -- it turns back to TYPE_NULL.
- if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
- value.dataType = Res_value::TYPE_NULL;
- value.data = Res_value::DATA_NULL_UNDEFINED;
- }
-
- //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
-
- // Write the final value back to Java.
- outValues[STYLE_TYPE] = value.dataType;
- outValues[STYLE_DATA] = value.data;
- outValues[STYLE_ASSET_COOKIE] = block != kXmlBlock
- ? static_cast<uint32_t>(res->getTableCookie(block)) : static_cast<uint32_t>(-1);
- outValues[STYLE_RESOURCE_ID] = resid;
- outValues[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
- outValues[STYLE_DENSITY] = config.density;
-
- if (outIndices != NULL && value.dataType != Res_value::TYPE_NULL) {
- indicesIdx++;
- outIndices[indicesIdx] = ii;
- }
-
- outValues += STYLE_NUM_ENTRIES;
+ // Try to find a value for this attribute...
+ // Skip through XML attributes until the end or the next possible match.
+ while (ix < xml_attr_count && cur_ident > cur_xml_attr) {
+ ix++;
+ cur_xml_attr = xml_parser->getAttributeNameResID(ix);
+ }
+ // Retrieve the current XML attribute if it matches, and step to next.
+ if (ix < xml_attr_count && cur_ident == cur_xml_attr) {
+ xml_parser->getAttributeValue(ix, &value);
+ ix++;
+ cur_xml_attr = xml_parser->getAttributeNameResID(ix);
}
- res->unlock();
-
- if (outIndices != NULL) {
- outIndices[0] = indicesIdx;
+ uint32_t resid = 0;
+ if (value.dataType != Res_value::TYPE_NULL) {
+ // Take care of resolving the found resource to its final value.
+ // printf("Resolving attribute reference\n");
+ ssize_t new_block = res->resolveReference(&value, block, &resid, &type_set_flags, &config);
+ if (new_block >= 0) block = new_block;
}
- return true;
+
+ // Deal with the special @null value -- it turns back to TYPE_NULL.
+ if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
+ value.dataType = Res_value::TYPE_NULL;
+ value.data = Res_value::DATA_NULL_UNDEFINED;
+ block = kXmlBlock;
+ }
+
+ // Write the final value back to Java.
+ out_values[STYLE_TYPE] = value.dataType;
+ out_values[STYLE_DATA] = value.data;
+ out_values[STYLE_ASSET_COOKIE] = block != kXmlBlock
+ ? static_cast<uint32_t>(res->getTableCookie(block))
+ : static_cast<uint32_t>(-1);
+ out_values[STYLE_RESOURCE_ID] = resid;
+ out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
+ out_values[STYLE_DENSITY] = config.density;
+
+ if (out_indices != NULL && value.dataType != Res_value::TYPE_NULL) {
+ indices_idx++;
+ out_indices[indices_idx] = ii;
+ }
+
+ out_values += STYLE_NUM_ENTRIES;
+ }
+
+ res->unlock();
+
+ if (out_indices != NULL) {
+ out_indices[0] = indices_idx;
+ }
+ return true;
}
-} // namespace android
+} // namespace android
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
index 1fe1773..6837f25 100644
--- a/libs/androidfw/tests/Android.mk
+++ b/libs/androidfw/tests/Android.mk
@@ -40,7 +40,7 @@
-Werror \
-Wunused \
-Wunreachable-code \
- -Wno-missing-field-initializers \
+ -Wno-missing-field-initializers
# gtest is broken.
androidfw_test_cflags += -Wno-unnamed-type-template-args
@@ -52,9 +52,10 @@
LOCAL_MODULE := libandroidfw_tests
LOCAL_CFLAGS := $(androidfw_test_cflags)
-LOCAL_SRC_FILES := $(testFiles)
+LOCAL_SRC_FILES := $(testFiles) AttributeResolution_test.cpp
LOCAL_STATIC_LIBRARIES := \
libandroidfw \
+ libbase \
libutils \
libcutils \
liblog \
@@ -76,6 +77,7 @@
LOCAL_SHARED_LIBRARIES := \
libandroidfw \
+ libbase \
libcutils \
libutils \
libui \
diff --git a/libs/androidfw/tests/AttributeFinder_test.cpp b/libs/androidfw/tests/AttributeFinder_test.cpp
index 5054624..d9ed48e 100644
--- a/libs/androidfw/tests/AttributeFinder_test.cpp
+++ b/libs/androidfw/tests/AttributeFinder_test.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 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.
@@ -14,115 +14,105 @@
* limitations under the License.
*/
-#include <androidfw/AttributeFinder.h>
+#include "../AttributeFinder.h"
+#include <android-base/macros.h>
#include <gtest/gtest.h>
using android::BackTrackingAttributeFinder;
class MockAttributeFinder : public BackTrackingAttributeFinder<MockAttributeFinder, int> {
-public:
- MockAttributeFinder(const uint32_t* attrs, int len)
- : BackTrackingAttributeFinder(0, len) {
- mAttrs = new uint32_t[len];
- memcpy(mAttrs, attrs, sizeof(*attrs) * len);
- }
+ public:
+ MockAttributeFinder(const uint32_t* attrs, int len) : BackTrackingAttributeFinder(0, len) {
+ attrs_ = new uint32_t[len];
+ memcpy(attrs_, attrs, sizeof(*attrs) * len);
+ }
- ~MockAttributeFinder() {
- delete mAttrs;
- }
+ ~MockAttributeFinder() { delete attrs_; }
- inline uint32_t getAttribute(const int index) const {
- return mAttrs[index];
- }
+ inline uint32_t GetAttribute(const int index) const { return attrs_[index]; }
-private:
- uint32_t* mAttrs;
+ private:
+ uint32_t* attrs_;
};
-static const uint32_t sortedAttributes[] = {
- 0x01010000, 0x01010001, 0x01010002, 0x01010004,
- 0x02010001, 0x02010010, 0x7f010001
-};
+static const uint32_t kSortedAttributes[] = {0x01010000, 0x01010001, 0x01010002, 0x01010004,
+ 0x02010001, 0x02010010, 0x7f010001};
-static const uint32_t packageUnsortedAttributes[] = {
- 0x02010001, 0x02010010, 0x01010000, 0x01010001,
- 0x01010002, 0x01010004, 0x7f010001
-};
+static const uint32_t kPackageUnsortedAttributes[] = {
+ 0x02010001, 0x02010010, 0x01010000, 0x01010001, 0x01010002, 0x01010004, 0x7f010001};
-static const uint32_t singlePackageAttributes[] = {
- 0x7f010007, 0x7f01000a, 0x7f01000d, 0x00000000
-};
+static const uint32_t kSinglePackageAttributes[] = {0x7f010007, 0x7f01000a, 0x7f01000d, 0x00000000};
TEST(AttributeFinderTest, IteratesSequentially) {
- const int end = sizeof(sortedAttributes) / sizeof(*sortedAttributes);
- MockAttributeFinder finder(sortedAttributes, end);
+ const int end = arraysize(kSortedAttributes);
+ MockAttributeFinder finder(kSortedAttributes, end);
- EXPECT_EQ(0, finder.find(0x01010000));
- EXPECT_EQ(1, finder.find(0x01010001));
- EXPECT_EQ(2, finder.find(0x01010002));
- EXPECT_EQ(3, finder.find(0x01010004));
- EXPECT_EQ(4, finder.find(0x02010001));
- EXPECT_EQ(5, finder.find(0x02010010));
- EXPECT_EQ(6, finder.find(0x7f010001));
- EXPECT_EQ(end, finder.find(0x7f010002));
+ EXPECT_EQ(0, finder.Find(0x01010000));
+ EXPECT_EQ(1, finder.Find(0x01010001));
+ EXPECT_EQ(2, finder.Find(0x01010002));
+ EXPECT_EQ(3, finder.Find(0x01010004));
+ EXPECT_EQ(4, finder.Find(0x02010001));
+ EXPECT_EQ(5, finder.Find(0x02010010));
+ EXPECT_EQ(6, finder.Find(0x7f010001));
+ EXPECT_EQ(end, finder.Find(0x7f010002));
}
TEST(AttributeFinderTest, PackagesAreOutOfOrder) {
- const int end = sizeof(sortedAttributes) / sizeof(*sortedAttributes);
- MockAttributeFinder finder(sortedAttributes, end);
+ const int end = arraysize(kSortedAttributes);
+ MockAttributeFinder finder(kSortedAttributes, end);
- EXPECT_EQ(6, finder.find(0x7f010001));
- EXPECT_EQ(end, finder.find(0x7f010002));
- EXPECT_EQ(4, finder.find(0x02010001));
- EXPECT_EQ(5, finder.find(0x02010010));
- EXPECT_EQ(0, finder.find(0x01010000));
- EXPECT_EQ(1, finder.find(0x01010001));
- EXPECT_EQ(2, finder.find(0x01010002));
- EXPECT_EQ(3, finder.find(0x01010004));
+ EXPECT_EQ(6, finder.Find(0x7f010001));
+ EXPECT_EQ(end, finder.Find(0x7f010002));
+ EXPECT_EQ(4, finder.Find(0x02010001));
+ EXPECT_EQ(5, finder.Find(0x02010010));
+ EXPECT_EQ(0, finder.Find(0x01010000));
+ EXPECT_EQ(1, finder.Find(0x01010001));
+ EXPECT_EQ(2, finder.Find(0x01010002));
+ EXPECT_EQ(3, finder.Find(0x01010004));
}
TEST(AttributeFinderTest, SomeAttributesAreNotFound) {
- const int end = sizeof(sortedAttributes) / sizeof(*sortedAttributes);
- MockAttributeFinder finder(sortedAttributes, end);
+ const int end = arraysize(kSortedAttributes);
+ MockAttributeFinder finder(kSortedAttributes, end);
- EXPECT_EQ(0, finder.find(0x01010000));
- EXPECT_EQ(1, finder.find(0x01010001));
- EXPECT_EQ(2, finder.find(0x01010002));
- EXPECT_EQ(end, finder.find(0x01010003));
- EXPECT_EQ(3, finder.find(0x01010004));
- EXPECT_EQ(end, finder.find(0x01010005));
- EXPECT_EQ(end, finder.find(0x01010006));
- EXPECT_EQ(4, finder.find(0x02010001));
- EXPECT_EQ(end, finder.find(0x02010002));
+ EXPECT_EQ(0, finder.Find(0x01010000));
+ EXPECT_EQ(1, finder.Find(0x01010001));
+ EXPECT_EQ(2, finder.Find(0x01010002));
+ EXPECT_EQ(end, finder.Find(0x01010003));
+ EXPECT_EQ(3, finder.Find(0x01010004));
+ EXPECT_EQ(end, finder.Find(0x01010005));
+ EXPECT_EQ(end, finder.Find(0x01010006));
+ EXPECT_EQ(4, finder.Find(0x02010001));
+ EXPECT_EQ(end, finder.Find(0x02010002));
}
TEST(AttributeFinderTest, FindAttributesInPackageUnsortedAttributeList) {
- const int end = sizeof(packageUnsortedAttributes) / sizeof(*packageUnsortedAttributes);
- MockAttributeFinder finder(packageUnsortedAttributes, end);
+ const int end = arraysize(kPackageUnsortedAttributes);
+ MockAttributeFinder finder(kPackageUnsortedAttributes, end);
- EXPECT_EQ(2, finder.find(0x01010000));
- EXPECT_EQ(3, finder.find(0x01010001));
- EXPECT_EQ(4, finder.find(0x01010002));
- EXPECT_EQ(end, finder.find(0x01010003));
- EXPECT_EQ(5, finder.find(0x01010004));
- EXPECT_EQ(end, finder.find(0x01010005));
- EXPECT_EQ(end, finder.find(0x01010006));
- EXPECT_EQ(0, finder.find(0x02010001));
- EXPECT_EQ(end, finder.find(0x02010002));
- EXPECT_EQ(1, finder.find(0x02010010));
- EXPECT_EQ(6, finder.find(0x7f010001));
+ EXPECT_EQ(2, finder.Find(0x01010000));
+ EXPECT_EQ(3, finder.Find(0x01010001));
+ EXPECT_EQ(4, finder.Find(0x01010002));
+ EXPECT_EQ(end, finder.Find(0x01010003));
+ EXPECT_EQ(5, finder.Find(0x01010004));
+ EXPECT_EQ(end, finder.Find(0x01010005));
+ EXPECT_EQ(end, finder.Find(0x01010006));
+ EXPECT_EQ(0, finder.Find(0x02010001));
+ EXPECT_EQ(end, finder.Find(0x02010002));
+ EXPECT_EQ(1, finder.Find(0x02010010));
+ EXPECT_EQ(6, finder.Find(0x7f010001));
}
TEST(AttributeFinderTest, FindAttributesInSinglePackageAttributeList) {
- const int end = sizeof(singlePackageAttributes) / sizeof(*singlePackageAttributes);
- MockAttributeFinder finder(singlePackageAttributes, end);
+ const int end = arraysize(kSinglePackageAttributes);
+ MockAttributeFinder finder(kSinglePackageAttributes, end);
- EXPECT_EQ(end, finder.find(0x010100f4));
- EXPECT_EQ(end, finder.find(0x010100f5));
- EXPECT_EQ(end, finder.find(0x010100f6));
- EXPECT_EQ(end, finder.find(0x010100f7));
- EXPECT_EQ(end, finder.find(0x010100f8));
- EXPECT_EQ(end, finder.find(0x010100fa));
- EXPECT_EQ(0, finder.find(0x7f010007));
+ EXPECT_EQ(end, finder.Find(0x010100f4));
+ EXPECT_EQ(end, finder.Find(0x010100f5));
+ EXPECT_EQ(end, finder.Find(0x010100f6));
+ EXPECT_EQ(end, finder.Find(0x010100f7));
+ EXPECT_EQ(end, finder.Find(0x010100f8));
+ EXPECT_EQ(end, finder.Find(0x010100fa));
+ EXPECT_EQ(0, finder.Find(0x7f010007));
}
diff --git a/libs/androidfw/tests/AttributeResolution_test.cpp b/libs/androidfw/tests/AttributeResolution_test.cpp
new file mode 100644
index 0000000..7fbe6d3
--- /dev/null
+++ b/libs/androidfw/tests/AttributeResolution_test.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "androidfw/AttributeResolution.h"
+#include "TestHelpers.h"
+#include "data/styles/R.h"
+
+#include <android-base/file.h>
+#include <android-base/macros.h>
+
+using namespace android;
+using android::base::ReadFileToString;
+using com::android::app::R;
+
+class AttributeResolutionTest : public ::testing::Test {
+ public:
+ virtual void SetUp() override {
+ std::string test_source_dir = TestSourceDir();
+ std::string contents;
+ LOG_ALWAYS_FATAL_IF(!ReadFileToString(test_source_dir + "/styles/resources.arsc", &contents));
+ LOG_ALWAYS_FATAL_IF(
+ table_.add(contents.data(), contents.size(), 1 /*cookie*/, true /*copyData*/) != NO_ERROR);
+ }
+
+ protected:
+ ResTable table_;
+};
+
+class AttributeResolutionXmlTest : public AttributeResolutionTest {
+ public:
+ virtual void SetUp() override {
+ AttributeResolutionTest::SetUp();
+ std::string test_source_dir = TestSourceDir();
+ std::string contents;
+ LOG_ALWAYS_FATAL_IF(!ReadFileToString(test_source_dir + "/styles/layout.xml", &contents));
+ LOG_ALWAYS_FATAL_IF(xml_parser_.setTo(contents.data(), contents.size(), true /*copyData*/) !=
+ NO_ERROR);
+
+ // Skip to the first tag.
+ while (xml_parser_.next() != ResXMLParser::START_TAG) {
+ }
+ }
+
+ protected:
+ ResXMLTree xml_parser_;
+};
+
+TEST_F(AttributeResolutionTest, Theme) {
+ ResTable::Theme theme(table_);
+ ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo));
+
+ uint32_t attrs[] = {R::attr::attr_one, R::attr::attr_two, R::attr::attr_three,
+ R::attr::attr_four};
+ std::vector<uint32_t> values;
+ values.resize(arraysize(attrs) * 6);
+
+ ASSERT_TRUE(ResolveAttrs(&theme, 0 /*def_style_attr*/, 0 /*def_style_res*/,
+ nullptr /*src_values*/, 0 /*src_values_length*/, attrs, arraysize(attrs),
+ values.data(), nullptr /*out_indices*/));
+
+ const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC;
+
+ const uint32_t* values_cursor = values.data();
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(1u, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+ values_cursor += STYLE_NUM_ENTRIES;
+ EXPECT_EQ(Res_value::TYPE_STRING, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+ values_cursor += STYLE_NUM_ENTRIES;
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(3u, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+ values_cursor += STYLE_NUM_ENTRIES;
+ EXPECT_EQ(Res_value::TYPE_NULL, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(Res_value::DATA_NULL_UNDEFINED, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(uint32_t(-1), values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(0u, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+}
+
+TEST_F(AttributeResolutionXmlTest, XmlParser) {
+ uint32_t attrs[] = {R::attr::attr_one, R::attr::attr_two, R::attr::attr_three,
+ R::attr::attr_four};
+ std::vector<uint32_t> values;
+ values.resize(arraysize(attrs) * 6);
+
+ ASSERT_TRUE(RetrieveAttributes(&table_, &xml_parser_, attrs, arraysize(attrs), values.data(),
+ nullptr /*out_indices*/));
+
+ uint32_t* values_cursor = values.data();
+ EXPECT_EQ(Res_value::TYPE_NULL, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(uint32_t(-1), values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(0u, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+ values_cursor += STYLE_NUM_ENTRIES;
+ EXPECT_EQ(Res_value::TYPE_NULL, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(uint32_t(-1), values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(0u, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+ values_cursor += STYLE_NUM_ENTRIES;
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(10u, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(uint32_t(-1), values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(0u, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+ values_cursor += STYLE_NUM_ENTRIES;
+ EXPECT_EQ(Res_value::TYPE_ATTRIBUTE, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(R::attr::attr_indirect, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(uint32_t(-1), values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(0u, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+}
+
+TEST_F(AttributeResolutionXmlTest, ThemeAndXmlParser) {
+ ResTable::Theme theme(table_);
+ ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo));
+
+ uint32_t attrs[] = {R::attr::attr_one, R::attr::attr_two, R::attr::attr_three, R::attr::attr_four,
+ R::attr::attr_five};
+ std::vector<uint32_t> values;
+ values.resize(arraysize(attrs) * 6);
+
+ ASSERT_TRUE(ApplyStyle(&theme, &xml_parser_, 0 /*def_style_attr*/, 0 /*def_style_res*/, attrs,
+ arraysize(attrs), values.data(), nullptr /*out_indices*/));
+
+ const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC;
+
+ uint32_t* values_cursor = values.data();
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(1u, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+ values_cursor += STYLE_NUM_ENTRIES;
+ EXPECT_EQ(Res_value::TYPE_STRING, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+ values_cursor += STYLE_NUM_ENTRIES;
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(10u, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(uint32_t(-1), values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(0u, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+ values_cursor += STYLE_NUM_ENTRIES;
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(3u, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+ values_cursor += STYLE_NUM_ENTRIES;
+ EXPECT_EQ(Res_value::TYPE_STRING, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(R::string::string_one, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+}
diff --git a/libs/androidfw/tests/TestHelpers.cpp b/libs/androidfw/tests/TestHelpers.cpp
index 41a19a7..3d1d5f5 100644
--- a/libs/androidfw/tests/TestHelpers.cpp
+++ b/libs/androidfw/tests/TestHelpers.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 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.
@@ -17,32 +17,46 @@
#include "TestHelpers.h"
#include <androidfw/ResourceTypes.h>
-#include <utils/String8.h>
#include <gtest/gtest.h>
+#include <unistd.h>
+#include <utils/String8.h>
+
+std::string TestSourceDir() {
+ const char* dir = getenv("ANDROID_BUILD_TOP");
+ LOG_ALWAYS_FATAL_IF(dir == nullptr, "Environment variable ANDROID_BUILD_TOP must be set");
+ std::string testdir = std::string(dir) + "/frameworks/base/libs/androidfw/tests/data";
+
+ // Check that the directory exists.
+ struct stat filestat;
+ LOG_ALWAYS_FATAL_IF(stat(testdir.c_str(), &filestat) != 0, "test data path '%s' does not exist",
+ testdir.c_str());
+ return testdir;
+}
namespace android {
-::testing::AssertionResult IsStringEqual(const ResTable& table, uint32_t resourceId, const char* expectedStr) {
- Res_value val;
- ssize_t block = table.getResource(resourceId, &val, MAY_NOT_BE_BAG);
- if (block < 0) {
- return ::testing::AssertionFailure() << "could not find resource";
- }
+::testing::AssertionResult IsStringEqual(const ResTable& table, uint32_t resource_id,
+ const char* expected_str) {
+ Res_value val;
+ ssize_t block = table.getResource(resource_id, &val, MAY_NOT_BE_BAG);
+ if (block < 0) {
+ return ::testing::AssertionFailure() << "could not find resource";
+ }
- if (val.dataType != Res_value::TYPE_STRING) {
- return ::testing::AssertionFailure() << "resource is not a string";
- }
+ if (val.dataType != Res_value::TYPE_STRING) {
+ return ::testing::AssertionFailure() << "resource is not a string";
+ }
- const ResStringPool* pool = table.getTableStringBlock(block);
- if (pool == NULL) {
- return ::testing::AssertionFailure() << "table has no string pool for block " << block;
- }
+ const ResStringPool* pool = table.getTableStringBlock(block);
+ if (pool == NULL) {
+ return ::testing::AssertionFailure() << "table has no string pool for block " << block;
+ }
- const String8 actual = pool->string8ObjectAt(val.data);
- if (String8(expectedStr) != actual) {
- return ::testing::AssertionFailure() << actual.string();
- }
- return ::testing::AssertionSuccess() << actual.string();
+ const String8 actual_str = pool->string8ObjectAt(val.data);
+ if (String8(expected_str) != actual_str) {
+ return ::testing::AssertionFailure() << actual_str.string();
+ }
+ return ::testing::AssertionSuccess() << actual_str.string();
}
-} // namespace android
+} // namespace android
diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h
index ff9be16..5f0c4552 100644
--- a/libs/androidfw/tests/TestHelpers.h
+++ b/libs/androidfw/tests/TestHelpers.h
@@ -1,35 +1,56 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
#ifndef __TEST_HELPERS_H
#define __TEST_HELPERS_H
-#include <ostream>
-
#include <androidfw/ResourceTypes.h>
-#include <utils/String8.h>
-#include <utils/String16.h>
#include <gtest/gtest.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+
+#include <ostream>
+#include <string>
+
+std::string TestSourceDir();
static inline ::std::ostream& operator<<(::std::ostream& out, const android::String8& str) {
- return out << str.string();
+ return out << str.string();
}
static inline ::std::ostream& operator<<(::std::ostream& out, const android::String16& str) {
- return out << android::String8(str).string();
+ return out << android::String8(str).string();
}
namespace android {
enum { MAY_NOT_BE_BAG = false };
-static inline bool operator==(const android::ResTable_config& a, const android::ResTable_config& b) {
- return a.compare(b) == 0;
+static inline bool operator==(const android::ResTable_config& a,
+ const android::ResTable_config& b) {
+ return a.compare(b) == 0;
}
static inline ::std::ostream& operator<<(::std::ostream& out, const android::ResTable_config& c) {
- return out << c.toString().string();
+ return out << c.toString().string();
}
-::testing::AssertionResult IsStringEqual(const ResTable& table, uint32_t resourceId, const char* expectedStr);
+::testing::AssertionResult IsStringEqual(const ResTable& table, uint32_t resource_id,
+ const char* expected_str);
-} // namespace android
+} // namespace android
-#endif // __TEST_HELPERS_H
+#endif // __TEST_HELPERS_H
diff --git a/libs/androidfw/tests/data/.gitignore b/libs/androidfw/tests/data/.gitignore
deleted file mode 100644
index c05cfb0..0000000
--- a/libs/androidfw/tests/data/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*.apk
-*.arsc
diff --git a/libs/androidfw/tests/data/styles/AndroidManifest.xml b/libs/androidfw/tests/data/styles/AndroidManifest.xml
new file mode 100644
index 0000000..5211316
--- /dev/null
+++ b/libs/androidfw/tests/data/styles/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.app">
+</manifest>
diff --git a/libs/androidfw/tests/data/styles/R.h b/libs/androidfw/tests/data/styles/R.h
new file mode 100644
index 0000000..6dc6ede
--- /dev/null
+++ b/libs/androidfw/tests/data/styles/R.h
@@ -0,0 +1,35 @@
+#include <cstdint>
+
+namespace com {
+namespace android {
+namespace app {
+
+struct R {
+ struct attr {
+ enum : uint32_t {
+ attr_one = 0x7f010000u,
+ attr_two = 0x7f010001u,
+ attr_three = 0x7f010002u,
+ attr_four = 0x7f010003u,
+ attr_five = 0x7f010004u,
+ attr_indirect = 0x7f010005u,
+ };
+ };
+
+ struct string {
+ enum : uint32_t {
+ string_one = 0x7f030000u,
+ };
+ };
+
+ struct style {
+ enum : uint32_t {
+ StyleOne = 0x7f020000u,
+ StyleTwo = 0x7f020001u,
+ };
+ };
+};
+
+} // namespace app
+} // namespace android
+} // namespace com
diff --git a/libs/androidfw/tests/data/styles/build.sh b/libs/androidfw/tests/data/styles/build.sh
new file mode 100755
index 0000000..e763421
--- /dev/null
+++ b/libs/androidfw/tests/data/styles/build.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+set -e
+
+aapt package -F package.apk -M AndroidManifest.xml -S res
+unzip -j package.apk resources.arsc res/layout/layout.xml
+rm package.apk
diff --git a/libs/androidfw/tests/data/styles/layout.xml b/libs/androidfw/tests/data/styles/layout.xml
new file mode 100644
index 0000000..4997e71
--- /dev/null
+++ b/libs/androidfw/tests/data/styles/layout.xml
Binary files differ
diff --git a/libs/androidfw/tests/data/styles/res/layout/layout.xml b/libs/androidfw/tests/data/styles/res/layout/layout.xml
new file mode 100644
index 0000000..f3aa0f8
--- /dev/null
+++ b/libs/androidfw/tests/data/styles/res/layout/layout.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<View xmlns:app="http://schemas.android.com/apk/res-auto"
+ app:attr_four="?attr/attr_indirect"
+ app:attr_three="10" />
+
diff --git a/libs/androidfw/tests/data/styles/res/values/styles.xml b/libs/androidfw/tests/data/styles/res/values/styles.xml
new file mode 100644
index 0000000..70c54f6
--- /dev/null
+++ b/libs/androidfw/tests/data/styles/res/values/styles.xml
@@ -0,0 +1,52 @@
+<?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.
+-->
+
+<resources>
+ <public type="attr" name="attr_one" id="0x7f010000" />
+ <attr name="attr_one" />
+
+ <public type="attr" name="attr_two" id="0x7f010001" />
+ <attr name="attr_two" />
+
+ <public type="attr" name="attr_three" id="0x7f010002" />
+ <attr name="attr_three" />
+
+ <public type="attr" name="attr_four" id="0x7f010003" />
+ <attr name="attr_four" />
+
+ <public type="attr" name="attr_five" id="0x7f010004" />
+ <attr name="attr_five" />
+
+ <public type="attr" name="attr_indirect" id="0x7f010005" />
+ <attr name="attr_indirect" />
+
+ <public type="string" name="string_one" id="0x7f030000" />
+ <string name="string_one">Hi</string>
+
+ <public type="style" name="StyleOne" id="0x7f020000" />
+ <style name="StyleOne">
+ <item name="attr_one">1</item>
+ </style>
+
+ <public type="style" name="StyleTwo" id="0x7f020001" />
+ <style name="StyleTwo" parent="@style/StyleOne">
+ <item name="attr_indirect">3</item>
+ <item name="attr_two">"string"</item>
+ <item name="attr_three">?attr/attr_indirect</item>
+ <item name="attr_five">@string/string_one</item>
+ </style>
+
+</resources>
diff --git a/libs/androidfw/tests/data/styles/resources.arsc b/libs/androidfw/tests/data/styles/resources.arsc
new file mode 100644
index 0000000..8f65c9a
--- /dev/null
+++ b/libs/androidfw/tests/data/styles/resources.arsc
Binary files differ
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 1282846..0aa7808 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -10,13 +10,13 @@
HWUI_ENABLE_OPENGL_VALIDATION := false
hwui_src_files := \
+ hwui/Bitmap.cpp \
font/CacheTexture.cpp \
font/Font.cpp \
hwui/Canvas.cpp \
hwui/MinikinSkia.cpp \
hwui/MinikinUtils.cpp \
hwui/PaintImpl.cpp \
- hwui/PixelRef.cpp \
hwui/Typeface.cpp \
renderstate/Blend.cpp \
renderstate/MeshState.cpp \
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 840c79d..6079d5d 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -46,7 +46,7 @@
const MergedBakedOpList& opList) {
const BakedOpState& firstState = *(opList.states[0]);
- const SkBitmap* bitmap = (static_cast<const BitmapOp*>(opList.states[0]->op))->bitmap;
+ Bitmap* bitmap = (static_cast<const BitmapOp*>(opList.states[0]->op))->bitmap;
Texture* texture = renderer.caches().textureCache.get(bitmap);
if (!texture) return;
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index ac7a600..e8972aa 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -181,7 +181,7 @@
if (!mRenderTarget.frameBufferId) mHasDrawn = true;
}
-Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) {
+Texture* BakedOpRenderer::getTexture(Bitmap* bitmap) {
return mCaches.textureCache.get(bitmap);
}
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index 62bc564..4d76a3d 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -74,7 +74,7 @@
void endLayer();
WARN_UNUSED_RESULT OffscreenBuffer* copyToLayer(const Rect& area);
- Texture* getTexture(const SkBitmap* bitmap);
+ Texture* getTexture(Bitmap* bitmap);
const LightInfo& getLightInfo() const { return mLightInfo; }
void renderGlop(const BakedOpState& state, const Glop& glop) {
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index 6e7d11f..5213d48 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -106,9 +106,9 @@
bool DisplayList::prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer,
std::function<void(RenderNode*, TreeInfo&, bool)> childFn) {
TextureCache& cache = Caches::getInstance().textureCache;
- for (auto&& bitmapResource : bitmapResources) {
+ for (auto& bitmapResource : bitmapResources) {
void* ownerToken = &info.canvasContext;
- info.prepareTextures = cache.prefetchAndMarkInUse(ownerToken, bitmapResource);
+ info.prepareTextures = cache.prefetchAndMarkInUse(ownerToken, bitmapResource.get());
}
for (auto&& op : children) {
RenderNode* childNode = op->renderNode;
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 06b0891..cab092f 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -38,6 +38,7 @@
#include "Matrix.h"
#include "RenderProperties.h"
#include "TreeInfo.h"
+#include "hwui/Bitmap.h"
#include <vector>
@@ -101,7 +102,7 @@
const LsaVector<NodeOpType*>& getChildren() const { return children; }
- const LsaVector<const SkBitmap*>& getBitmapResources() const { return bitmapResources; }
+ const LsaVector<sk_sp<Bitmap>>& getBitmapResources() const { return bitmapResources; }
size_t addChild(NodeOpType* childOp);
@@ -140,7 +141,7 @@
LsaVector<NodeOpType*> children;
// Resources - Skia objects + 9 patches referred to by this DisplayList
- LsaVector<const SkBitmap*> bitmapResources;
+ LsaVector<sk_sp<Bitmap>> bitmapResources;
LsaVector<const SkPath*> pathResources;
LsaVector<const Res_png_9patch*> patchResources;
LsaVector<std::unique_ptr<const SkPaint>> paints;
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index f35d605..245db1d 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -631,7 +631,7 @@
}
void FrameBuilder::deferVectorDrawableOp(const VectorDrawableOp& op) {
- const SkBitmap& bitmap = op.vectorDrawable->getBitmapUpdateIfDirty();
+ Bitmap& bitmap = op.vectorDrawable->getBitmapUpdateIfDirty();
SkPaint* paint = op.vectorDrawable->getPaint();
const BitmapRectOp* resolvedOp = mAllocator.create_trivial<BitmapRectOp>(op.unmappedBounds,
op.localMatrix,
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index 3b1caa5..f9a7c36f2 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -211,14 +211,14 @@
};
struct BitmapOp : RecordedOp {
- BitmapOp(BASE_PARAMS, const SkBitmap* bitmap)
+ BitmapOp(BASE_PARAMS, Bitmap* bitmap)
: SUPER(BitmapOp)
, bitmap(bitmap) {}
- const SkBitmap* bitmap;
+ Bitmap* bitmap;
};
struct BitmapMeshOp : RecordedOp {
- BitmapMeshOp(BASE_PARAMS, const SkBitmap* bitmap, int meshWidth, int meshHeight,
+ BitmapMeshOp(BASE_PARAMS, Bitmap* bitmap, int meshWidth, int meshHeight,
const float* vertices, const int* colors)
: SUPER(BitmapMeshOp)
, bitmap(bitmap)
@@ -226,7 +226,7 @@
, meshHeight(meshHeight)
, vertices(vertices)
, colors(colors) {}
- const SkBitmap* bitmap;
+ Bitmap* bitmap;
const int meshWidth;
const int meshHeight;
const float* vertices;
@@ -234,11 +234,11 @@
};
struct BitmapRectOp : RecordedOp {
- BitmapRectOp(BASE_PARAMS, const SkBitmap* bitmap, const Rect& src)
+ BitmapRectOp(BASE_PARAMS, Bitmap* bitmap, const Rect& src)
: SUPER(BitmapRectOp)
, bitmap(bitmap)
, src(src) {}
- const SkBitmap* bitmap;
+ Bitmap* bitmap;
const Rect src;
};
@@ -288,11 +288,11 @@
};
struct PatchOp : RecordedOp {
- PatchOp(BASE_PARAMS, const SkBitmap* bitmap, const Res_png_9patch* patch)
+ PatchOp(BASE_PARAMS, Bitmap* bitmap, const Res_png_9patch* patch)
: SUPER(PatchOp)
, bitmap(bitmap)
, patch(patch) {}
- const SkBitmap* bitmap;
+ Bitmap* bitmap;
const Res_png_9patch* patch;
};
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 09d5252..f5bcba2 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -36,7 +36,7 @@
"Destroyed a RecordingCanvas during a record!");
}
-void RecordingCanvas::resetRecording(int width, int height) {
+void RecordingCanvas::resetRecording(int width, int height, RenderNode* node) {
LOG_ALWAYS_FATAL_IF(mDisplayList,
"prepareDirty called a second time during a recording!");
mDisplayList = new DisplayList();
@@ -468,17 +468,17 @@
}
// Bitmap-based
-void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) {
+void RecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) {
save(SaveFlags::Matrix);
translate(left, top);
- drawBitmap(&bitmap, paint);
+ drawBitmap(bitmap, paint);
restore();
}
-void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
+void RecordingCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix,
const SkPaint* paint) {
if (matrix.isIdentity()) {
- drawBitmap(&bitmap, paint);
+ drawBitmap(bitmap, paint);
} else if (!(matrix.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask))
&& MathUtils::isPositive(matrix.getScaleX())
&& MathUtils::isPositive(matrix.getScaleY())) {
@@ -492,12 +492,12 @@
} else {
save(SaveFlags::Matrix);
concat(matrix);
- drawBitmap(&bitmap, paint);
+ drawBitmap(bitmap, paint);
restore();
}
}
-void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
+void RecordingCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop,
float srcRight, float srcBottom, float dstLeft, float dstTop,
float dstRight, float dstBottom, const SkPaint* paint) {
if (srcLeft == 0 && srcTop == 0
@@ -508,7 +508,7 @@
// transform simple rect to rect drawing case into position bitmap ops, since they merge
save(SaveFlags::Matrix);
translate(dstLeft, dstTop);
- drawBitmap(&bitmap, paint);
+ drawBitmap(bitmap, paint);
restore();
} else {
addOp(alloc().create_trivial<BitmapRectOp>(
@@ -520,7 +520,7 @@
}
}
-void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
+void RecordingCanvas::drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight,
const float* vertices, const int* colors, const SkPaint* paint) {
int vertexCount = (meshWidth + 1) * (meshHeight + 1);
addOp(alloc().create_trivial<BitmapMeshOp>(
@@ -532,7 +532,7 @@
refBuffer<int>(colors, vertexCount))); // 1 color per vertex
}
-void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& patch,
+void RecordingCanvas::drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& patch,
float dstLeft, float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) {
addOp(alloc().create_trivial<PatchOp>(
@@ -575,12 +575,12 @@
}
}
-void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
+void RecordingCanvas::drawBitmap(Bitmap& bitmap, const SkPaint* paint) {
addOp(alloc().create_trivial<BitmapOp>(
- Rect(bitmap->width(), bitmap->height()),
+ Rect(bitmap.width(), bitmap.height()),
*(mState.currentSnapshot()->transform),
getRecordedClip(),
- refPaint(paint), refBitmap(*bitmap)));
+ refPaint(paint), refBitmap(bitmap)));
}
void RecordingCanvas::drawRenderNode(RenderNode* renderNode) {
@@ -666,7 +666,9 @@
SkBitmap bitmap;
SkShader::TileMode xy[2];
if (shader->isABitmap(&bitmap, nullptr, xy)) {
- refBitmap(bitmap);
+ // TODO: create hwui-owned BitmapShader.
+ Bitmap* hwuiBitmap = static_cast<Bitmap*>(bitmap.pixelRef());
+ refBitmap(*hwuiBitmap);
return;
}
SkShader::ComposeRec rec;
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 4483e1b..a8fcfeb 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -22,6 +22,7 @@
#include "ResourceCache.h"
#include "SkiaCanvasProxy.h"
#include "Snapshot.h"
+#include "hwui/Bitmap.h"
#include "hwui/Canvas.h"
#include "utils/LinearAllocator.h"
#include "utils/Macros.h"
@@ -50,7 +51,7 @@
RecordingCanvas(size_t width, size_t height);
virtual ~RecordingCanvas();
- virtual void resetRecording(int width, int height) override;
+ virtual void resetRecording(int width, int height, RenderNode* node = nullptr) override;
virtual WARN_UNUSED_RESULT DisplayList* finishRecording() override;
// ----------------------------------------------------------------------------
// MISC HWUI OPERATIONS - TODO: CATEGORIZE
@@ -176,15 +177,14 @@
virtual void drawVectorDrawable(VectorDrawableRoot* tree) override;
// Bitmap-based
- virtual void drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) override;
- virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
- const SkPaint* paint) override;
- virtual void drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
+ virtual void drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) override;
+ virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint) override;
+ virtual void drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop,
float srcRight, float srcBottom, float dstLeft, float dstTop,
float dstRight, float dstBottom, const SkPaint* paint) override;
- virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
+ virtual void drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight,
const float* vertices, const int* colors, const SkPaint* paint) override;
- virtual void drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
+ virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk,
float dstLeft, float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
@@ -204,7 +204,7 @@
return mState.writableSnapshot()->mutateClipArea().serializeClip(alloc());
}
- void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
+ void drawBitmap(Bitmap& bitmap, const SkPaint* paint);
void drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint);
@@ -286,14 +286,17 @@
return cachedRegion;
}
- inline const SkBitmap* refBitmap(const SkBitmap& bitmap) {
+ inline Bitmap* refBitmap(Bitmap& bitmap) {
// Note that this assumes the bitmap is immutable. There are cases this won't handle
// correctly, such as creating the bitmap from scratch, drawing with it, changing its
// contents, and drawing again. The only fix would be to always copy it the first time,
// which doesn't seem worth the extra cycles for this unlikely case.
- SkBitmap* localBitmap = alloc().create<SkBitmap>(bitmap);
- mDisplayList->bitmapResources.push_back(localBitmap);
- return localBitmap;
+
+ // this is required because sk_sp's ctor adopts the pointer,
+ // but does not increment the refcount,
+ bitmap.ref();
+ mDisplayList->bitmapResources.emplace_back(&bitmap);
+ return &bitmap;
}
inline const Res_png_9patch* refPatch(const Res_png_9patch* patch) {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 7b2fda1..11aaebd 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -18,6 +18,7 @@
#include "CanvasProperty.h"
#include "VectorDrawable.h"
+#include "hwui/Bitmap.h"
#include "hwui/MinikinUtils.h"
#include <SkDrawable.h>
@@ -30,11 +31,14 @@
#include <SkRSXform.h>
#include <SkShader.h>
#include <SkTemplates.h>
+#include <SkTextBlob.h>
#include <memory>
namespace android {
+using uirenderer::PaintUtils;
+
Canvas* Canvas::create_canvas(const SkBitmap& bitmap) {
return new SkiaCanvas(bitmap);
}
@@ -43,6 +47,11 @@
return new SkiaCanvas(skiaCanvas);
}
+SkiaCanvas::SkiaCanvas() {}
+
+SkiaCanvas::SkiaCanvas(SkCanvas* canvas)
+ : mCanvas(SkRef(canvas)) {}
+
SkiaCanvas::SkiaCanvas(const SkBitmap& bitmap) {
mCanvas.reset(new SkCanvas(bitmap));
}
@@ -76,21 +85,21 @@
};
void SkiaCanvas::setBitmap(const SkBitmap& bitmap) {
- sk_sp<SkCanvas> newCanvas(new SkCanvas(bitmap));
+ SkCanvas* newCanvas = new SkCanvas(bitmap);
if (!bitmap.isNull()) {
// Copy the canvas matrix & clip state.
newCanvas->setMatrix(mCanvas->getTotalMatrix());
- ClipCopier copier(newCanvas.get());
+ ClipCopier copier(newCanvas);
mCanvas->replayClips(&copier);
}
// unrefs the existing canvas
- mCanvas = std::move(newCanvas);
+ mCanvas.reset(newCanvas);
// clean up the old save stack
- mSaveStack.reset(NULL);
+ mSaveStack.reset(nullptr);
}
// ----------------------------------------------------------------------------
@@ -128,13 +137,8 @@
// operation. It does this by explicitly saving off the clip & matrix state
// when requested and playing it back after the SkCanvas::restore.
void SkiaCanvas::restore() {
- const SaveRec* rec = (NULL == mSaveStack.get())
- ? NULL
- : static_cast<SaveRec*>(mSaveStack->back());
- int currentSaveCount = mCanvas->getSaveCount();
- SkASSERT(NULL == rec || currentSaveCount >= rec->saveCount);
-
- if (NULL == rec || rec->saveCount != currentSaveCount) {
+ const auto* rec = this->currentSaveRec();
+ if (!rec) {
// Fast path - no record for this frame.
mCanvas->restore();
return;
@@ -148,27 +152,18 @@
savedMatrix = mCanvas->getTotalMatrix();
}
- SkTArray<SkClipStack::Element> savedClips;
- int topClipStackFrame = mCanvas->getClipStack()->getSaveCount();
- if (preserveClip) {
- saveClipsForFrame(savedClips, topClipStackFrame);
- }
+ const size_t clipIndex = rec->clipIndex;
mCanvas->restore();
+ mSaveStack->pop_back();
if (preserveMatrix) {
mCanvas->setMatrix(savedMatrix);
}
- if (preserveClip && !savedClips.empty() &&
- topClipStackFrame != mCanvas->getClipStack()->getSaveCount()) {
- // Only reapply the saved clips if the top clip stack frame was actually
- // popped by restore(). If it wasn't, it means it doesn't belong to the
- // restored canvas frame (SkCanvas lazy save/restore kicked in).
- applyClips(savedClips);
+ if (preserveClip) {
+ this->applyPersistentClips(clipIndex);
}
-
- mSaveStack->pop_back();
}
void SkiaCanvas::restoreToCount(int restoreCount) {
@@ -212,6 +207,56 @@
return this->saveLayer(left, top, right, bottom, nullptr, flags);
}
+class SkiaCanvas::Clip {
+public:
+ Clip(const SkRect& rect, SkRegion::Op op, const SkMatrix& m)
+ : mType(Type::Rect), mOp(op), mMatrix(m), mRRect(SkRRect::MakeRect(rect)) {}
+ Clip(const SkRRect& rrect, SkRegion::Op op, const SkMatrix& m)
+ : mType(Type::RRect), mOp(op), mMatrix(m), mRRect(rrect) {}
+ Clip(const SkPath& path, SkRegion::Op op, const SkMatrix& m)
+ : mType(Type::Path), mOp(op), mMatrix(m), mPath(&path) {}
+
+ void apply(SkCanvas* canvas) const {
+ canvas->setMatrix(mMatrix);
+ switch (mType) {
+ case Type::Rect:
+ canvas->clipRect(mRRect.rect(), mOp);
+ break;
+ case Type::RRect:
+ canvas->clipRRect(mRRect, mOp);
+ break;
+ case Type::Path:
+ canvas->clipPath(*mPath.get(), mOp);
+ break;
+ }
+ }
+
+private:
+ enum class Type {
+ Rect,
+ RRect,
+ Path,
+ };
+
+ Type mType;
+ SkRegion::Op mOp;
+ SkMatrix mMatrix;
+
+ // These are logically a union (tracked separately due to non-POD path).
+ SkTLazy<SkPath> mPath;
+ SkRRect mRRect;
+};
+
+const SkiaCanvas::SaveRec* SkiaCanvas::currentSaveRec() const {
+ const SaveRec* rec = mSaveStack
+ ? static_cast<const SaveRec*>(mSaveStack->back())
+ : nullptr;
+ int currentSaveCount = mCanvas->getSaveCount();
+ SkASSERT(!rec || currentSaveCount >= rec->saveCount);
+
+ return (rec && rec->saveCount == currentSaveCount) ? rec : nullptr;
+}
+
// ----------------------------------------------------------------------------
// functions to emulate legacy SaveFlags (i.e. independent matrix/clip flags)
// ----------------------------------------------------------------------------
@@ -228,45 +273,48 @@
return;
}
- if (NULL == mSaveStack.get()) {
+ if (!mSaveStack) {
mSaveStack.reset(new SkDeque(sizeof(struct SaveRec), 8));
}
SaveRec* rec = static_cast<SaveRec*>(mSaveStack->push_back());
rec->saveCount = mCanvas->getSaveCount();
rec->saveFlags = flags;
+ rec->clipIndex = mClipStack.size();
}
-void SkiaCanvas::saveClipsForFrame(SkTArray<SkClipStack::Element>& clips,
- int saveCountToBackup) {
- // Each SkClipStack::Element stores the index of the canvas save
- // with which it is associated. Backup only those Elements that
- // are associated with 'saveCountToBackup'
- SkClipStack::Iter clipIterator(*mCanvas->getClipStack(),
- SkClipStack::Iter::kTop_IterStart);
- while (const SkClipStack::Element* elem = clipIterator.prev()) {
- if (elem->getSaveCount() < saveCountToBackup) {
- // done with the target save count.
- break;
- }
- SkASSERT(elem->getSaveCount() == saveCountToBackup);
- clips.push_back(*elem);
+template <typename T>
+void SkiaCanvas::recordClip(const T& clip, SkRegion::Op op) {
+ // Only need tracking when in a partial save frame which
+ // doesn't restore the clip.
+ const SaveRec* rec = this->currentSaveRec();
+ if (rec && !(rec->saveFlags & SaveFlags::Clip)) {
+ mClipStack.emplace_back(clip, op, mCanvas->getTotalMatrix());
}
}
-void SkiaCanvas::applyClips(const SkTArray<SkClipStack::Element>& clips) {
- ClipCopier clipCopier(mCanvas.get());
+// Applies and optionally removes all clips >= index.
+void SkiaCanvas::applyPersistentClips(size_t clipStartIndex) {
+ SkASSERT(clipStartIndex <= mClipStack.size());
+ const auto begin = mClipStack.cbegin() + clipStartIndex;
+ const auto end = mClipStack.cend();
- // The clip stack stores clips in device space.
- SkMatrix origMatrix = mCanvas->getTotalMatrix();
- mCanvas->resetMatrix();
+ // Clip application mutates the CTM.
+ const SkMatrix saveMatrix = mCanvas->getTotalMatrix();
- // We pushed the clips in reverse order.
- for (int i = clips.count() - 1; i >= 0; --i) {
- clips[i].replay(&clipCopier);
+ for (auto clip = begin; clip != end; ++clip) {
+ clip->apply(mCanvas.get());
}
- mCanvas->setMatrix(origMatrix);
+ mCanvas->setMatrix(saveMatrix);
+
+ // If the current/post-restore save rec is also persisting clips, we
+ // leave them on the stack to be reapplied part of the next restore().
+ // Otherwise we're done and just pop them.
+ const auto* rec = this->currentSaveRec();
+ if (!rec || (rec->saveFlags & SaveFlags::Clip)) {
+ mClipStack.erase(begin, end);
+ }
}
// ----------------------------------------------------------------------------
@@ -342,6 +390,7 @@
bool SkiaCanvas::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
+ this->recordClip(rect, op);
mCanvas->clipRect(rect, op);
return !mCanvas->isClipEmpty();
}
@@ -349,8 +398,10 @@
bool SkiaCanvas::clipPath(const SkPath* path, SkRegion::Op op) {
SkRRect roundRect;
if (path->isRRect(&roundRect)) {
+ this->recordClip(roundRect, op);
mCanvas->clipRRect(roundRect, op);
} else {
+ this->recordClip(*path, op);
mCanvas->clipPath(*path, op);
}
return !mCanvas->isClipEmpty();
@@ -362,10 +413,13 @@
// The region is specified in device space.
SkMatrix savedMatrix = mCanvas->getTotalMatrix();
mCanvas->resetMatrix();
+ this->recordClip(rgnPath, op);
mCanvas->clipPath(rgnPath, op);
mCanvas->setMatrix(savedMatrix);
} else {
- mCanvas->clipRect(SkRect::MakeEmpty(), op);
+ const auto emptyClip = SkRect::MakeEmpty();
+ this->recordClip(emptyClip, op);
+ mCanvas->clipRect(emptyClip, op);
}
return !mCanvas->isClipEmpty();
}
@@ -400,6 +454,7 @@
void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint,
SkCanvas::PointMode mode) {
+ if (CC_UNLIKELY(count < 2 || PaintUtils::paintWillNotDraw(paint))) return;
// convert the floats into SkPoints
count >>= 1; // now it is the number of points
std::unique_ptr<SkPoint[]> pts(new SkPoint[count]);
@@ -425,45 +480,49 @@
}
void SkiaCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
+ if (CC_UNLIKELY(count < 4 || PaintUtils::paintWillNotDraw(paint))) return;
this->drawPoints(points, count, paint, SkCanvas::kLines_PointMode);
}
void SkiaCanvas::drawRect(float left, float top, float right, float bottom,
const SkPaint& paint) {
+ if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
mCanvas->drawRectCoords(left, top, right, bottom, paint);
}
void SkiaCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
- SkRegion::Iterator it(region);
- while (!it.done()) {
- mCanvas->drawRect(SkRect::Make(it.rect()), paint);
- it.next();
- }
+ if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ mCanvas->drawRegion(region, paint);
}
void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, const SkPaint& paint) {
+ if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
mCanvas->drawRoundRect(rect, rx, ry, paint);
}
void SkiaCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
+ if (CC_UNLIKELY(radius <= 0 || PaintUtils::paintWillNotDraw(paint))) return;
mCanvas->drawCircle(x, y, radius, paint);
}
void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
+ if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
mCanvas->drawOval(oval, paint);
}
void SkiaCanvas::drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
+ if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
SkRect arc = SkRect::MakeLTRB(left, top, right, bottom);
mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, paint);
}
void SkiaCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
+ if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
SkRect rect;
SkRRect roundRect;
if (path.isOval(&rect)) {
@@ -490,27 +549,34 @@
// Canvas draw operations: Bitmaps
// ----------------------------------------------------------------------------
-void SkiaCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) {
- mCanvas->drawBitmap(bitmap, left, top, paint);
+void SkiaCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) {
+ SkBitmap skBitmap;
+ bitmap.getSkBitmap(&skBitmap);
+ mCanvas->drawBitmap(skBitmap, left, top, paint);
}
-void SkiaCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint) {
+void SkiaCanvas::drawBitmap(Bitmap& hwuiBitmap, const SkMatrix& matrix, const SkPaint* paint) {
+ SkBitmap bitmap;
+ hwuiBitmap.getSkBitmap(&bitmap);
SkAutoCanvasRestore acr(mCanvas.get(), true);
mCanvas->concat(matrix);
mCanvas->drawBitmap(bitmap, 0, 0, paint);
}
-void SkiaCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
+void SkiaCanvas::drawBitmap(Bitmap& hwuiBitmap, float srcLeft, float srcTop,
float srcRight, float srcBottom, float dstLeft, float dstTop,
float dstRight, float dstBottom, const SkPaint* paint) {
+ SkBitmap bitmap;
+ hwuiBitmap.getSkBitmap(&bitmap);
SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
mCanvas->drawBitmapRect(bitmap, srcRect, dstRect, paint);
}
-void SkiaCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
+void SkiaCanvas::drawBitmapMesh(Bitmap& hwuiBitmap, int meshWidth, int meshHeight,
const float* vertices, const int* colors, const SkPaint* paint) {
-
+ SkBitmap bitmap;
+ hwuiBitmap.getSkBitmap(&bitmap);
const int ptCount = (meshWidth + 1) * (meshHeight + 1);
const int indexCount = meshWidth * meshHeight * 6;
@@ -595,23 +661,113 @@
if (paint) {
tmpPaint = *paint;
}
- sk_sp<SkShader> shader = SkMakeBitmapShader(bitmap,
- SkShader::kClamp_TileMode,
- SkShader::kClamp_TileMode,
- nullptr,
- kNever_SkCopyPixelsMode,
- nullptr);
- tmpPaint.setShader(std::move(shader));
+
+ sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
+ tmpPaint.setShader(image->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode));
mCanvas->drawVertices(SkCanvas::kTriangles_VertexMode, ptCount, (SkPoint*)vertices,
texs, (const SkColor*)colors, NULL, indices,
indexCount, tmpPaint);
}
-void SkiaCanvas::drawNinePatch(const SkBitmap& bitmap, const Res_png_9patch& chunk,
+static inline void set_lattice_divs(SkCanvas::Lattice* lattice, const Res_png_9patch& chunk,
+ int width, int height) {
+ lattice->fXCount = chunk.numXDivs;
+ lattice->fYCount = chunk.numYDivs;
+ lattice->fXDivs = chunk.getXDivs();
+ lattice->fYDivs = chunk.getYDivs();
+
+ // We'll often see ninepatches where the last div is equal to the width or height.
+ // This doesn't provide any additional information and is not supported by Skia.
+ if (lattice->fXCount > 0 && width == lattice->fXDivs[lattice->fXCount - 1]) {
+ lattice->fXCount--;
+ }
+ if (lattice->fYCount > 0 && height == lattice->fYDivs[lattice->fYCount - 1]) {
+ lattice->fYCount--;
+ }
+}
+
+static inline int num_distinct_rects(const SkCanvas::Lattice& lattice) {
+ int xRects;
+ if (lattice.fXCount > 0) {
+ xRects = (0 == lattice.fXDivs[0]) ? lattice.fXCount : lattice.fXCount + 1;
+ } else {
+ xRects = 1;
+ }
+
+ int yRects;
+ if (lattice.fYCount > 0) {
+ yRects = (0 == lattice.fYDivs[0]) ? lattice.fYCount : lattice.fYCount + 1;
+ } else {
+ yRects = 1;
+ }
+ return xRects * yRects;
+}
+
+static inline void set_lattice_flags(SkCanvas::Lattice* lattice, SkCanvas::Lattice::Flags* flags,
+ int numFlags, const Res_png_9patch& chunk) {
+ lattice->fFlags = flags;
+ sk_bzero(flags, numFlags * sizeof(SkCanvas::Lattice::Flags));
+
+ bool needPadRow = lattice->fYCount > 0 && 0 == lattice->fYDivs[0];
+ bool needPadCol = lattice->fXCount > 0 && 0 == lattice->fXDivs[0];
+
+ int yCount = lattice->fYCount;
+ if (needPadRow) {
+ // Skip flags for the degenerate first row of rects.
+ flags += lattice->fXCount + 1;
+ yCount--;
+ }
+
+ int i = 0;
+ bool setFlags = false;
+ for (int y = 0; y < yCount + 1; y++) {
+ for (int x = 0; x < lattice->fXCount + 1; x++) {
+ if (0 == x && needPadCol) {
+ // First rect of each column is degenerate, skip the flag.
+ flags++;
+ continue;
+ }
+
+ if (0 == chunk.getColors()[i++]) {
+ *flags = SkCanvas::Lattice::kTransparent_Flags;
+ setFlags = true;
+ }
+
+ flags++;
+ }
+ }
+
+ if (!setFlags) {
+ lattice->fFlags = nullptr;
+ }
+}
+
+void SkiaCanvas::drawNinePatch(Bitmap& hwuiBitmap, const Res_png_9patch& chunk,
float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) {
- SkRect bounds = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- NinePatch::Draw(mCanvas.get(), bounds, bitmap, chunk, paint, nullptr);
+
+ SkBitmap bitmap;
+ hwuiBitmap.getSkBitmap(&bitmap);
+
+ SkCanvas::Lattice lattice;
+ set_lattice_divs(&lattice, chunk, bitmap.width(), bitmap.height());
+
+ lattice.fFlags = nullptr;
+ int numFlags = 0;
+ if (chunk.numColors > 0 && chunk.numColors == num_distinct_rects(lattice)) {
+ // We can expect the framework to give us a color for every distinct rect.
+ // Skia requires a flag for every rect.
+ numFlags = (lattice.fXCount + 1) * (lattice.fYCount + 1);
+ }
+
+ SkAutoSTMalloc<25, SkCanvas::Lattice::Flags> flags(numFlags);
+ if (numFlags > 0) {
+ set_lattice_flags(&lattice, flags.get(), numFlags, chunk);
+ }
+
+ lattice.fBounds = nullptr;
+ SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
+ mCanvas->drawBitmapLattice(bitmap, lattice, dst, paint);
}
void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) {
@@ -626,9 +782,26 @@
const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) {
- static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
- mCanvas->drawPosText(text, count << 1, reinterpret_cast<const SkPoint*>(positions), paint);
- drawTextDecorations(x, y, totalAdvance, paint);
+ if (!text || !positions || count <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
+ // Set align to left for drawing, as we don't want individual
+ // glyphs centered or right-aligned; the offset above takes
+ // care of all alignment.
+ SkPaint paintCopy(paint);
+ paintCopy.setTextAlign(SkPaint::kLeft_Align);
+
+ SkRect bounds = SkRect::MakeLTRB(boundsLeft + x, boundsTop + y,
+ boundsRight + x, boundsBottom + y);
+
+ SkTextBlobBuilder builder;
+ const SkTextBlobBuilder::RunBuffer& buffer = builder.allocRunPos(paintCopy, count, &bounds);
+ // TODO: we could reduce the number of memcpy's if the this were exposed further up
+ // in the architecture.
+ memcpy(buffer.glyphs, text, count * sizeof(uint16_t));
+ memcpy(buffer.pos, positions, (count << 1) * sizeof(float));
+
+ sk_sp<SkTextBlob> textBlob(builder.make());
+ mCanvas->drawTextBlob(textBlob, 0, 0, paintCopy);
+ drawTextDecorations(x, y, totalAdvance, paintCopy);
}
void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
@@ -733,7 +906,7 @@
// Canvas draw operations: View System
// ----------------------------------------------------------------------------
-void SkiaCanvas::drawLayer(uirenderer::DeferredLayerUpdater* layer) {
+void SkiaCanvas::drawLayer(uirenderer::DeferredLayerUpdater* layerUpdater) {
LOG_ALWAYS_FATAL("SkiaCanvas can't directly draw Layers");
}
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index aac9036..d1edff9 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -22,8 +22,7 @@
#include "VectorDrawable.h"
#include <SkCanvas.h>
-#include <SkClipStack.h>
-#include <SkTArray.h>
+#include <SkTLazy.h>
namespace android {
@@ -39,16 +38,14 @@
* not be NULL. This constructor will ref() the SkCanvas, and unref()
* it in its destructor.
*/
- explicit SkiaCanvas(SkCanvas* canvas) : mCanvas(canvas) {
- SkASSERT(canvas);
- canvas->ref();
- }
+ explicit SkiaCanvas(SkCanvas* canvas);
virtual SkCanvas* asSkCanvas() override {
return mCanvas.get();
}
- virtual void resetRecording(int width, int height) override {
+ virtual void resetRecording(int width, int height,
+ uirenderer::RenderNode* renderNode) override {
LOG_ALWAYS_FATAL("SkiaCanvas cannot be reset as a recording canvas");
}
@@ -123,16 +120,14 @@
const float* verts, const float* tex, const int* colors,
const uint16_t* indices, int indexCount, const SkPaint& paint) override;
- virtual void drawBitmap(const SkBitmap& bitmap, float left, float top,
- const SkPaint* paint) override;
- virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
- const SkPaint* paint) override;
- virtual void drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
+ virtual void drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) override;
+ virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint) override;
+ virtual void drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop,
float srcRight, float srcBottom, float dstLeft, float dstTop,
float dstRight, float dstBottom, const SkPaint* paint) override;
- virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
+ virtual void drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight,
const float* vertices, const int* colors, const SkPaint* paint) override;
- virtual void drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
+ virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk,
float dstLeft, float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
@@ -153,7 +148,7 @@
uirenderer::GlFunctorLifecycleListener* listener) override;
protected:
- explicit SkiaCanvas() {}
+ SkiaCanvas();
void reset(SkCanvas* skiaCanvas);
void drawDrawable(SkDrawable* drawable) { mCanvas->drawDrawable(drawable); }
@@ -168,19 +163,26 @@
struct SaveRec {
int saveCount;
SaveFlags::Flags saveFlags;
+ size_t clipIndex;
};
bool mHighContrastText = false;
+ const SaveRec* currentSaveRec() const;
void recordPartialSave(SaveFlags::Flags flags);
- void saveClipsForFrame(SkTArray<SkClipStack::Element>& clips, int frameSaveCount);
- void applyClips(const SkTArray<SkClipStack::Element>& clips);
+
+ template<typename T>
+ void recordClip(const T&, SkRegion::Op);
+ void applyPersistentClips(size_t clipStartIndex);
void drawPoints(const float* points, int count, const SkPaint& paint,
SkCanvas::PointMode mode);
- sk_sp<SkCanvas> mCanvas;
+ class Clip;
+
+ sk_sp<SkCanvas> mCanvas;
std::unique_ptr<SkDeque> mSaveStack; // lazily allocated, tracks partial saves.
+ std::vector<Clip> mClipStack; // tracks persistent clips.
};
} // namespace android
diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp
index fded604..863146e 100644
--- a/libs/hwui/SkiaCanvasProxy.cpp
+++ b/libs/hwui/SkiaCanvasProxy.cpp
@@ -16,6 +16,8 @@
#include "SkiaCanvasProxy.h"
+#include "hwui/Bitmap.h"
+
#include <cutils/log.h>
#include <SkPatchUtils.h>
#include <SkPaint.h>
@@ -105,16 +107,12 @@
void SkiaCanvasProxy::onDrawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
const SkPaint* paint) {
- SkPixelRef* pxRef = bitmap.pixelRef();
-
+ sk_sp<Bitmap> hwuiBitmap = Bitmap::createFrom(bitmap.info(), *bitmap.pixelRef());
// HWUI doesn't support extractSubset(), so convert any subsetted bitmap into
// a drawBitmapRect(); pass through an un-subsetted bitmap.
- if (pxRef && bitmap.dimensions() != pxRef->info().dimensions()) {
- SkBitmap fullBitmap;
- fullBitmap.setInfo(pxRef->info());
- fullBitmap.setPixelRef(pxRef, 0, 0);
+ if (hwuiBitmap && bitmap.dimensions() != hwuiBitmap->info().dimensions()) {
SkIPoint origin = bitmap.pixelRefOrigin();
- mCanvas->drawBitmap(fullBitmap, origin.fX, origin.fY,
+ mCanvas->drawBitmap(*hwuiBitmap, origin.fX, origin.fY,
origin.fX + bitmap.dimensions().width(),
origin.fY + bitmap.dimensions().height(),
left, top,
@@ -122,15 +120,16 @@
top + bitmap.dimensions().height(),
paint);
} else {
- mCanvas->drawBitmap(bitmap, left, top, paint);
+ mCanvas->drawBitmap(*hwuiBitmap, left, top, paint);
}
}
-void SkiaCanvasProxy::onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* srcPtr,
+void SkiaCanvasProxy::onDrawBitmapRect(const SkBitmap& skBitmap, const SkRect* srcPtr,
const SkRect& dst, const SkPaint* paint, SrcRectConstraint) {
- SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(bitmap.width(), bitmap.height());
+ SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(skBitmap.width(), skBitmap.height());
// TODO: if bitmap is a subset, do we need to add pixelRefOrigin to src?
- mCanvas->drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
+ Bitmap* bitmap = reinterpret_cast<Bitmap*>(skBitmap.pixelRef());
+ mCanvas->drawBitmap(*bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint);
}
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 5d9e5c0..489a306 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -21,6 +21,7 @@
#include "Layer.h"
#include "Matrix.h"
#include "Texture.h"
+#include "hwui/Bitmap.h"
#include <SkMatrix.h>
#include <utils/Log.h>
@@ -206,7 +207,9 @@
return false;
}
- outData->bitmapTexture = caches.textureCache.get(&bitmap);
+ // TODO: create hwui-owned BitmapShader.
+ Bitmap* hwuiBitmap = static_cast<Bitmap*>(bitmap.pixelRef());
+ outData->bitmapTexture = caches.textureCache.get(hwuiBitmap);
if (!outData->bitmapTexture) return false;
outData->bitmapSampler = (*textureUnit)++;
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 5ccdbda..08641b7 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -23,6 +23,7 @@
#include "TextureCache.h"
#include "Properties.h"
#include "utils/TraceUtils.h"
+#include "hwui/Bitmap.h"
namespace android {
namespace uirenderer {
@@ -91,7 +92,7 @@
}
}
-bool TextureCache::canMakeTextureFromBitmap(const SkBitmap* bitmap) {
+bool TextureCache::canMakeTextureFromBitmap(Bitmap* bitmap) {
if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) {
ALOGW("Bitmap too large to be uploaded into a texture (%dx%d, max=%dx%d)",
bitmap->width(), bitmap->height(), mMaxTextureSize, mMaxTextureSize);
@@ -102,8 +103,8 @@
// Returns a prepared Texture* that either is already in the cache or can fit
// in the cache (and is thus added to the cache)
-Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap) {
- Texture* texture = mCache.get(bitmap->pixelRef()->getStableID());
+Texture* TextureCache::getCachedTexture(Bitmap* bitmap) {
+ Texture* texture = mCache.get(bitmap->getStableID());
if (!texture) {
if (!canMakeTextureFromBitmap(bitmap)) {
@@ -126,7 +127,9 @@
texture = new Texture(Caches::getInstance());
texture->bitmapSize = size;
texture->generation = bitmap->getGenerationID();
- texture->upload(*bitmap);
+ SkBitmap skBitmap;
+ bitmap->getSkBitmap(&skBitmap);
+ texture->upload(skBitmap);
mSize += size;
TEXTURE_LOGD("TextureCache::get: create texture(%p): name, size, mSize = %d, %d, %d",
@@ -134,19 +137,21 @@
if (mDebugEnabled) {
ALOGD("Texture created, size = %d", size);
}
- mCache.put(bitmap->pixelRef()->getStableID(), texture);
+ mCache.put(bitmap->getStableID(), texture);
}
} else if (!texture->isInUse && bitmap->getGenerationID() != texture->generation) {
// Texture was in the cache but is dirty, re-upload
// TODO: Re-adjust the cache size if the bitmap's dimensions have changed
- texture->upload(*bitmap);
+ SkBitmap skBitmap;
+ bitmap->getSkBitmap(&skBitmap);
+ texture->upload(skBitmap);
texture->generation = bitmap->getGenerationID();
}
return texture;
}
-bool TextureCache::prefetchAndMarkInUse(void* ownerToken, const SkBitmap* bitmap) {
+bool TextureCache::prefetchAndMarkInUse(void* ownerToken, Bitmap* bitmap) {
Texture* texture = getCachedTexture(bitmap);
if (texture) {
texture->isInUse = ownerToken;
@@ -154,11 +159,11 @@
return texture;
}
-bool TextureCache::prefetch(const SkBitmap* bitmap) {
+bool TextureCache::prefetch(Bitmap* bitmap) {
return getCachedTexture(bitmap);
}
-Texture* TextureCache::get(const SkBitmap* bitmap) {
+Texture* TextureCache::get(Bitmap* bitmap) {
Texture* texture = getCachedTexture(bitmap);
if (!texture) {
@@ -169,7 +174,9 @@
const uint32_t size = bitmap->rowBytes() * bitmap->height();
texture = new Texture(Caches::getInstance());
texture->bitmapSize = size;
- texture->upload(*bitmap);
+ SkBitmap skBitmap;
+ bitmap->getSkBitmap(&skBitmap);
+ texture->upload(skBitmap);
texture->generation = bitmap->getGenerationID();
texture->cleanup = true;
}
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index 88ef771..68a548b 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -28,6 +28,9 @@
#include <unordered_map>
namespace android {
+
+class Bitmap;
+
namespace uirenderer {
class Texture;
@@ -73,20 +76,20 @@
* acquired for the bitmap, false otherwise. If a Texture was acquired it is
* marked as in use.
*/
- bool prefetchAndMarkInUse(void* ownerToken, const SkBitmap* bitmap);
+ bool prefetchAndMarkInUse(void* ownerToken, Bitmap* bitmap);
/**
* Attempts to precache the SkBitmap. Returns true if a Texture was successfully
* acquired for the bitmap, false otherwise. Does not mark the Texture
* as in use and won't update currently in-use Textures.
*/
- bool prefetch(const SkBitmap* bitmap);
+ bool prefetch(Bitmap* bitmap);
/**
* Returns the texture associated with the specified bitmap from within the cache.
* If the texture cannot be found in the cache, a new texture is generated.
*/
- Texture* get(const SkBitmap* bitmap);
+ Texture* get(Bitmap* bitmap);
/**
* Removes the texture associated with the specified pixelRef. This is meant
@@ -119,9 +122,9 @@
void flush();
private:
- bool canMakeTextureFromBitmap(const SkBitmap* bitmap);
+ bool canMakeTextureFromBitmap(Bitmap* bitmap);
- Texture* getCachedTexture(const SkBitmap* bitmap);
+ Texture* getCachedTexture(Bitmap* bitmap);
LruCache<uint32_t, Texture*> mCache;
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 715681d..b50647a 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -502,18 +502,18 @@
}
void Tree::drawStaging(Canvas* outCanvas) {
- bool redrawNeeded = allocateBitmapIfNeeded(&mStagingCache.bitmap,
+ bool redrawNeeded = allocateBitmapIfNeeded(mStagingCache,
mStagingProperties.getScaledWidth(), mStagingProperties.getScaledHeight());
// draw bitmap cache
if (redrawNeeded || mStagingCache.dirty) {
- updateBitmapCache(&mStagingCache.bitmap, true);
+ updateBitmapCache(*mStagingCache.bitmap, true);
mStagingCache.dirty = false;
}
SkPaint tmpPaint;
SkPaint* paint = updatePaint(&tmpPaint, &mStagingProperties);
- outCanvas->drawBitmap(mStagingCache.bitmap, 0, 0,
- mStagingCache.bitmap.width(), mStagingCache.bitmap.height(),
+ outCanvas->drawBitmap(*mStagingCache.bitmap, 0, 0,
+ mStagingCache.bitmap->width(), mStagingCache.bitmap->height(),
mStagingProperties.getBounds().left(), mStagingProperties.getBounds().top(),
mStagingProperties.getBounds().right(), mStagingProperties.getBounds().bottom(), paint);
}
@@ -535,46 +535,46 @@
}
}
-const SkBitmap& Tree::getBitmapUpdateIfDirty() {
- bool redrawNeeded = allocateBitmapIfNeeded(&mCache.bitmap, mProperties.getScaledWidth(),
+Bitmap& Tree::getBitmapUpdateIfDirty() {
+ bool redrawNeeded = allocateBitmapIfNeeded(mCache, mProperties.getScaledWidth(),
mProperties.getScaledHeight());
if (redrawNeeded || mCache.dirty) {
- updateBitmapCache(&mCache.bitmap, false);
+ updateBitmapCache(*mCache.bitmap, false);
mCache.dirty = false;
}
- return mCache.bitmap;
+ return *mCache.bitmap;
}
-void Tree::updateBitmapCache(SkBitmap* outCache, bool useStagingData) {
- outCache->eraseColor(SK_ColorTRANSPARENT);
- SkCanvas outCanvas(*outCache);
+void Tree::updateBitmapCache(Bitmap& bitmap, bool useStagingData) {
+ SkBitmap outCache;
+ bitmap.getSkBitmap(&outCache);
+ outCache.eraseColor(SK_ColorTRANSPARENT);
+ SkCanvas outCanvas(outCache);
float viewportWidth = useStagingData ?
mStagingProperties.getViewportWidth() : mProperties.getViewportWidth();
float viewportHeight = useStagingData ?
mStagingProperties.getViewportHeight() : mProperties.getViewportHeight();
- float scaleX = outCache->width() / viewportWidth;
- float scaleY = outCache->height() / viewportHeight;
+ float scaleX = outCache.width() / viewportWidth;
+ float scaleY = outCache.height() / viewportHeight;
mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY, useStagingData);
}
-bool Tree::allocateBitmapIfNeeded(SkBitmap* outCache, int width, int height) {
- if (!canReuseBitmap(*outCache, width, height)) {
+bool Tree::allocateBitmapIfNeeded(Cache& cache, int width, int height) {
+ if (!canReuseBitmap(cache.bitmap.get(), width, height)) {
#ifndef ANDROID_ENABLE_LINEAR_BLENDING
sk_sp<SkColorSpace> colorSpace = nullptr;
#else
sk_sp<SkColorSpace> colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
#endif
SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, colorSpace);
- outCache->setInfo(info);
- // TODO: Count the bitmap cache against app's java heap
- outCache->allocPixels(info);
+ cache.bitmap = Bitmap::allocateHeapBitmap(info);
return true;
}
return false;
}
-bool Tree::canReuseBitmap(const SkBitmap& bitmap, int width, int height) {
- return width == bitmap.width() && height == bitmap.height();
+bool Tree::canReuseBitmap(Bitmap* bitmap, int width, int height) {
+ return bitmap && width == bitmap->width() && height == bitmap->height();
}
void Tree::onPropertyChanged(TreeProperties* prop) {
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index e68fbf4..e9a9c71 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -18,6 +18,7 @@
#define ANDROID_HWUI_VPATH_H
#include "hwui/Canvas.h"
+#include "hwui/Bitmap.h"
#include "DisplayList.h"
#include <SkBitmap.h>
@@ -561,7 +562,7 @@
const SkRect& bounds, bool needsMirroring, bool canReuseCache);
void drawStaging(Canvas* canvas);
- const SkBitmap& getBitmapUpdateIfDirty();
+ Bitmap& getBitmapUpdateIfDirty();
void setAllowCaching(bool allowCaching) {
mAllowCaching = allowCaching;
}
@@ -691,11 +692,15 @@
void setPropertyChangeWillBeConsumed(bool willBeConsumed) { mWillBeConsumed = willBeConsumed; }
private:
+ struct Cache {
+ sk_sp<Bitmap> bitmap;
+ bool dirty = true;
+ };
SkPaint* updatePaint(SkPaint* outPaint, TreeProperties* prop);
- bool allocateBitmapIfNeeded(SkBitmap* outCache, int width, int height);
- bool canReuseBitmap(const SkBitmap&, int width, int height);
- void updateBitmapCache(SkBitmap* outCache, bool useStagingData);
+ bool allocateBitmapIfNeeded(Cache& cache, int width, int height);
+ bool canReuseBitmap(Bitmap*, int width, int height);
+ void updateBitmapCache(Bitmap& outCache, bool useStagingData);
// Cap the bitmap size, such that it won't hurt the performance too much
// and it won't crash due to a very large scale.
// The drawable will look blurry above this size.
@@ -708,10 +713,6 @@
TreeProperties mStagingProperties = TreeProperties(this);
SkPaint mPaint;
- struct Cache {
- SkBitmap bitmap;
- bool dirty = true;
- };
Cache mStagingCache;
Cache mCache;
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
new file mode 100644
index 0000000..31fbe68
--- /dev/null
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -0,0 +1,277 @@
+/*
+ * 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.
+ */
+#include "Bitmap.h"
+
+#include "Caches.h"
+
+#include <cutils/log.h>
+#include <sys/mman.h>
+#include <cutils/ashmem.h>
+
+namespace android {
+
+static bool computeAllocationSize(size_t rowBytes, int height, size_t* size) {
+ int32_t rowBytes32 = SkToS32(rowBytes);
+ int64_t bigSize = (int64_t) height * rowBytes32;
+ if (rowBytes32 < 0 || !sk_64_isS32(bigSize)) {
+ return false; // allocation will be too large
+ }
+
+ *size = sk_64_asS32(bigSize);
+ return true;
+}
+
+typedef sk_sp<Bitmap> (*AllocPixeRef)(size_t allocSize, const SkImageInfo& info, size_t rowBytes,
+ SkColorTable* ctable);
+
+static sk_sp<Bitmap> allocateBitmap(SkBitmap* bitmap, SkColorTable* ctable, AllocPixeRef alloc) {
+ const SkImageInfo& info = bitmap->info();
+ if (info.colorType() == kUnknown_SkColorType) {
+ LOG_ALWAYS_FATAL("unknown bitmap configuration");
+ return nullptr;
+ }
+
+ size_t size;
+
+ // we must respect the rowBytes value already set on the bitmap instead of
+ // attempting to compute our own.
+ const size_t rowBytes = bitmap->rowBytes();
+ if (!computeAllocationSize(rowBytes, bitmap->height(), &size)) {
+ return nullptr;
+ }
+
+ auto wrapper = alloc(size, info, rowBytes, ctable);
+ if (wrapper) {
+ wrapper->getSkBitmap(bitmap);
+ // since we're already allocated, we lockPixels right away
+ // HeapAllocator behaves this way too
+ bitmap->lockPixels();
+ }
+ return wrapper;
+}
+
+sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(SkBitmap* bitmap, SkColorTable* ctable) {
+ return allocateBitmap(bitmap, ctable, &Bitmap::allocateAshmemBitmap);
+}
+
+static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes,
+ SkColorTable* ctable) {
+ void* addr = calloc(size, 1);
+ if (!addr) {
+ return nullptr;
+ }
+ return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes, ctable));
+}
+
+sk_sp<Bitmap> Bitmap::allocateHeapBitmap(SkBitmap* bitmap, SkColorTable* ctable) {
+ return allocateBitmap(bitmap, ctable, &android::allocateHeapBitmap);
+}
+
+sk_sp<Bitmap> Bitmap::allocateHeapBitmap(const SkImageInfo& info) {
+ size_t size;
+ if (!computeAllocationSize(info.minRowBytes(), info.height(), &size)) {
+ LOG_ALWAYS_FATAL("trying to allocate too large bitmap");
+ return nullptr;
+ }
+ return android::allocateHeapBitmap(size, info, info.minRowBytes(), nullptr);
+}
+
+sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info,
+ size_t rowBytes, SkColorTable* ctable) {
+ // Create new ashmem region with read/write priv
+ int fd = ashmem_create_region("bitmap", size);
+ if (fd < 0) {
+ return nullptr;
+ }
+
+ void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (addr == MAP_FAILED) {
+ close(fd);
+ return nullptr;
+ }
+
+ if (ashmem_set_prot_region(fd, PROT_READ) < 0) {
+ munmap(addr, size);
+ close(fd);
+ return nullptr;
+ }
+ return sk_sp<Bitmap>(new Bitmap(addr, fd, size, info, rowBytes, ctable));
+}
+
+void FreePixelRef(void* addr, void* context) {
+ auto pixelRef = (SkPixelRef*) context;
+ pixelRef->unlockPixels();
+ pixelRef->unref();
+}
+
+sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, SkPixelRef& pixelRef) {
+ pixelRef.ref();
+ pixelRef.lockPixels();
+ return sk_sp<Bitmap>(new Bitmap((void*) pixelRef.pixels(), (void*) &pixelRef, FreePixelRef,
+ info, pixelRef.rowBytes(), pixelRef.colorTable()));
+}
+
+void Bitmap::reconfigure(const SkImageInfo& newInfo, size_t rowBytes, SkColorTable* ctable) {
+ if (kIndex_8_SkColorType != newInfo.colorType()) {
+ ctable = nullptr;
+ }
+ mRowBytes = rowBytes;
+ if (mColorTable.get() != ctable) {
+ mColorTable.reset(ctable);
+ }
+
+ // Need to validate the alpha type to filter against the color type
+ // to prevent things like a non-opaque RGB565 bitmap
+ SkAlphaType alphaType;
+ LOG_ALWAYS_FATAL_IF(!SkColorTypeValidateAlphaType(
+ newInfo.colorType(), newInfo.alphaType(), &alphaType),
+ "Failed to validate alpha type!");
+
+ // Dirty hack is dirty
+ // TODO: Figure something out here, Skia's current design makes this
+ // really hard to work with. Skia really, really wants immutable objects,
+ // but with the nested-ref-count hackery going on that's just not
+ // feasible without going insane trying to figure it out
+ SkImageInfo* myInfo = const_cast<SkImageInfo*>(&this->info());
+ *myInfo = newInfo;
+ changeAlphaType(alphaType);
+
+ // Docs say to only call this in the ctor, but we're going to call
+ // it anyway even if this isn't always the ctor.
+ // TODO: Fix this too as part of the above TODO
+ setPreLocked(getStorage(), mRowBytes, mColorTable.get());
+}
+
+Bitmap::Bitmap(void* address, size_t size, const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
+ : SkPixelRef(info)
+ , mPixelStorageType(PixelStorageType::Heap) {
+ mPixelStorage.heap.address = address;
+ mPixelStorage.heap.size = size;
+ reconfigure(info, rowBytes, ctable);
+}
+
+Bitmap::Bitmap(void* address, void* context, FreeFunc freeFunc,
+ const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
+ : SkPixelRef(info)
+ , mPixelStorageType(PixelStorageType::External) {
+ mPixelStorage.external.address = address;
+ mPixelStorage.external.context = context;
+ mPixelStorage.external.freeFunc = freeFunc;
+ reconfigure(info, rowBytes, ctable);
+}
+
+Bitmap::Bitmap(void* address, int fd, size_t mappedSize,
+ const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
+ : SkPixelRef(info)
+ , mPixelStorageType(PixelStorageType::Ashmem) {
+ mPixelStorage.ashmem.address = address;
+ mPixelStorage.ashmem.fd = fd;
+ mPixelStorage.ashmem.size = mappedSize;
+ reconfigure(info, rowBytes, ctable);
+}
+
+Bitmap::~Bitmap() {
+ switch (mPixelStorageType) {
+ case PixelStorageType::External:
+ mPixelStorage.external.freeFunc(mPixelStorage.external.address,
+ mPixelStorage.external.context);
+ break;
+ case PixelStorageType::Ashmem:
+ munmap(mPixelStorage.ashmem.address, mPixelStorage.ashmem.size);
+ close(mPixelStorage.ashmem.fd);
+ break;
+ case PixelStorageType::Heap:
+ free(mPixelStorage.heap.address);
+ break;
+ }
+
+ if (android::uirenderer::Caches::hasInstance()) {
+ android::uirenderer::Caches::getInstance().textureCache.releaseTexture(getStableID());
+ }
+}
+
+bool Bitmap::hasHardwareMipMap() const {
+ return mHasHardwareMipMap;
+}
+
+void Bitmap::setHasHardwareMipMap(bool hasMipMap) {
+ mHasHardwareMipMap = hasMipMap;
+}
+
+void* Bitmap::getStorage() const {
+ switch (mPixelStorageType) {
+ case PixelStorageType::External:
+ return mPixelStorage.external.address;
+ case PixelStorageType::Ashmem:
+ return mPixelStorage.ashmem.address;
+ case PixelStorageType::Heap:
+ return mPixelStorage.heap.address;
+ }
+}
+
+bool Bitmap::onNewLockPixels(LockRec* rec) {
+ rec->fPixels = getStorage();
+ rec->fRowBytes = mRowBytes;
+ rec->fColorTable = mColorTable.get();
+ return true;
+}
+
+size_t Bitmap::getAllocatedSizeInBytes() const {
+ return info().getSafeSize(mRowBytes);
+}
+
+int Bitmap::getAshmemFd() const {
+ switch (mPixelStorageType) {
+ case PixelStorageType::Ashmem:
+ return mPixelStorage.ashmem.fd;
+ default:
+ return -1;
+ }
+}
+
+size_t Bitmap::getAllocationByteCount() const {
+ switch (mPixelStorageType) {
+ case PixelStorageType::Heap:
+ return mPixelStorage.heap.size;
+ default:
+ return rowBytes() * height();
+ }
+}
+
+void Bitmap::reconfigure(const SkImageInfo& info) {
+ reconfigure(info, info.minRowBytes(), nullptr);
+}
+
+void Bitmap::setAlphaType(SkAlphaType alphaType) {
+ if (!SkColorTypeValidateAlphaType(info().colorType(), alphaType, &alphaType)) {
+ return;
+ }
+
+ changeAlphaType(alphaType);
+}
+
+void Bitmap::getSkBitmap(SkBitmap* outBitmap) {
+ outBitmap->setInfo(info(), rowBytes());
+ outBitmap->setPixelRef(this);
+ outBitmap->setHasHardwareMipMap(mHasHardwareMipMap);
+}
+
+void Bitmap::getBounds(SkRect* bounds) const {
+ SkASSERT(bounds);
+ bounds->set(0, 0, SkIntToScalar(info().width()), SkIntToScalar(info().height()));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/hwui/hwui/PixelRef.h b/libs/hwui/hwui/Bitmap.h
similarity index 72%
rename from libs/hwui/hwui/PixelRef.h
rename to libs/hwui/hwui/Bitmap.h
index 3f8aea6..e86ac11 100644
--- a/libs/hwui/hwui/PixelRef.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -31,13 +31,21 @@
typedef void (*FreeFunc)(void* addr, void* context);
-class ANDROID_API PixelRef : public SkPixelRef {
+class ANDROID_API Bitmap : public SkPixelRef {
public:
- PixelRef(void* address, size_t allocSize, const SkImageInfo& info, size_t rowBytes,
+ static sk_sp<Bitmap> allocateHeapBitmap(SkBitmap* bitmap, SkColorTable* ctable);
+ static sk_sp<Bitmap> allocateHeapBitmap(const SkImageInfo& info);
+
+ static sk_sp<Bitmap> allocateAshmemBitmap(SkBitmap* bitmap, SkColorTable* ctable);
+ static sk_sp<Bitmap> allocateAshmemBitmap(size_t allocSize, const SkImageInfo& info,
+ size_t rowBytes, SkColorTable* ctable);
+
+ static sk_sp<Bitmap> createFrom(const SkImageInfo&, SkPixelRef&);
+ Bitmap(void* address, size_t allocSize, const SkImageInfo& info, size_t rowBytes,
SkColorTable* ctable);
- PixelRef(void* address, void* context, FreeFunc freeFunc,
+ Bitmap(void* address, void* context, FreeFunc freeFunc,
const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable);
- PixelRef(void* address, int fd, size_t mappedSize, const SkImageInfo& info,
+ Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info,
size_t rowBytes, SkColorTable* ctable);
int width() const { return info().width(); }
@@ -46,7 +54,7 @@
// Can't mark as override since SkPixelRef::rowBytes isn't virtual
// but that's OK since we just want Bitmap to be able to rely
// on calling rowBytes() on an unlocked pixelref, which it will be
- // doing on a PixelRef type, not a SkPixelRef, so static
+ // doing on a Bitmap type, not a SkPixelRef, so static
// dispatching will do what we want.
size_t rowBytes() const { return mRowBytes; }
void reconfigure(const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable);
@@ -58,17 +66,21 @@
int getAshmemFd() const;
size_t getAllocationByteCount() const;
+ void setHasHardwareMipMap(bool hasMipMap);
+ bool hasHardwareMipMap() const;
+
+ bool isOpaque() const {return info().isOpaque(); }
+ SkColorType colorType() const { return info().colorType(); }
+ void getBounds(SkRect* bounds) const;
+
protected:
virtual bool onNewLockPixels(LockRec* rec) override;
virtual void onUnlockPixels() override { };
virtual size_t getAllocatedSizeInBytes() const override;
private:
- friend class Bitmap;
- virtual ~PixelRef();
+ virtual ~Bitmap();
void doFreePixels();
void* getStorage() const;
- void setHasHardwareMipMap(bool hasMipMap);
- bool hasHardwareMipMap() const;
PixelStorageType mPixelStorageType;
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index b18e794..1ea8bd2 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -17,6 +17,7 @@
#include "Canvas.h"
#include "RecordingCanvas.h"
+#include "RenderNode.h"
#include "MinikinUtils.h"
#include "Paint.h"
#include "Typeface.h"
@@ -25,7 +26,7 @@
namespace android {
-Canvas* Canvas::create_recording_canvas(int width, int height) {
+Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::RenderNode* renderNode) {
return new uirenderer::RecordingCanvas(width, height);
}
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index cb9056f..f275ce1 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -65,6 +65,7 @@
};
typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot;
+class Bitmap;
class Paint;
struct Typeface;
@@ -74,7 +75,23 @@
static Canvas* create_canvas(const SkBitmap& bitmap);
- static WARN_UNUSED_RESULT Canvas* create_recording_canvas(int width, int height);
+ /**
+ * Create a new Canvas object that records view system drawing operations for deferred
+ * rendering. A canvas returned by this call supports calls to the resetRecording(...) and
+ * finishRecording() calls. The latter call returns a DisplayList that is specific to the
+ * RenderPipeline defined by Properties::getRenderPipelineType().
+ *
+ * @param width of the requested Canvas.
+ * @param height of the requested Canvas.
+ * @param renderNode is an optional parameter that specifies the node that will consume the
+ * DisplayList produced by the returned Canvas. This enables the reuse of select C++
+ * objects as a speed optimization.
+ * @return new non-null Canvas Object. The type of DisplayList produced by this canvas is
+ determined based on Properties::getRenderPipelineType().
+ *
+ */
+ static WARN_UNUSED_RESULT Canvas* create_recording_canvas(int width, int height,
+ uirenderer::RenderNode* renderNode = nullptr);
/**
* Create a new Canvas object which delegates to an SkCanvas.
@@ -83,7 +100,8 @@
* delegated to this object. This function will call ref() on the
* SkCanvas, and the returned Canvas will unref() it upon
* destruction.
- * @return new Canvas object. Will not return NULL.
+ * @return new non-null Canvas Object. The type of DisplayList produced by this canvas is
+ * determined based on Properties::getRenderPipelineType().
*/
static Canvas* create_canvas(SkCanvas* skiaCanvas);
@@ -112,7 +130,8 @@
// View System operations (not exposed in public Canvas API)
// ----------------------------------------------------------------------------
- virtual void resetRecording(int width, int height) = 0;
+ virtual void resetRecording(int width, int height,
+ uirenderer::RenderNode* renderNode = nullptr) = 0;
virtual uirenderer::DisplayList* finishRecording() = 0;
virtual void insertReorderBarrier(bool enableReorder) = 0;
@@ -199,16 +218,16 @@
const uint16_t* indices, int indexCount, const SkPaint& paint) = 0;
// Bitmap-based
- virtual void drawBitmap(const SkBitmap& bitmap, float left, float top,
+ virtual void drawBitmap(Bitmap& bitmap, float left, float top,
const SkPaint* paint) = 0;
- virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
+ virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix,
const SkPaint* paint) = 0;
- virtual void drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
+ virtual void drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop,
float srcRight, float srcBottom, float dstLeft, float dstTop,
float dstRight, float dstBottom, const SkPaint* paint) = 0;
- virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
+ virtual void drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight,
const float* vertices, const int* colors, const SkPaint* paint) = 0;
- virtual void drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
+ virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk,
float dstLeft, float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) = 0;
diff --git a/libs/hwui/hwui/PixelRef.cpp b/libs/hwui/hwui/PixelRef.cpp
deleted file mode 100644
index 795de6b..0000000
--- a/libs/hwui/hwui/PixelRef.cpp
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * 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.
- */
-#include "PixelRef.h"
-
-#include "Caches.h"
-
-#include <cutils/log.h>
-#include <sys/mman.h>
-#include <cutils/ashmem.h>
-
-namespace android {
-
-void PixelRef::reconfigure(const SkImageInfo& newInfo, size_t rowBytes, SkColorTable* ctable) {
- if (kIndex_8_SkColorType != newInfo.colorType()) {
- ctable = nullptr;
- }
- mRowBytes = rowBytes;
- if (mColorTable.get() != ctable) {
- mColorTable.reset(ctable);
- }
-
- // Need to validate the alpha type to filter against the color type
- // to prevent things like a non-opaque RGB565 bitmap
- SkAlphaType alphaType;
- LOG_ALWAYS_FATAL_IF(!SkColorTypeValidateAlphaType(
- newInfo.colorType(), newInfo.alphaType(), &alphaType),
- "Failed to validate alpha type!");
-
- // Dirty hack is dirty
- // TODO: Figure something out here, Skia's current design makes this
- // really hard to work with. Skia really, really wants immutable objects,
- // but with the nested-ref-count hackery going on that's just not
- // feasible without going insane trying to figure it out
- SkImageInfo* myInfo = const_cast<SkImageInfo*>(&this->info());
- *myInfo = newInfo;
- changeAlphaType(alphaType);
-
- // Docs say to only call this in the ctor, but we're going to call
- // it anyway even if this isn't always the ctor.
- // TODO: Fix this too as part of the above TODO
- setPreLocked(getStorage(), mRowBytes, mColorTable.get());
-}
-
-PixelRef::PixelRef(void* address, size_t size, const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
- : SkPixelRef(info)
- , mPixelStorageType(PixelStorageType::Heap) {
- mPixelStorage.heap.address = address;
- mPixelStorage.heap.size = size;
- reconfigure(info, rowBytes, ctable);
-}
-
-PixelRef::PixelRef(void* address, void* context, FreeFunc freeFunc,
- const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
- : SkPixelRef(info)
- , mPixelStorageType(PixelStorageType::External) {
- mPixelStorage.external.address = address;
- mPixelStorage.external.context = context;
- mPixelStorage.external.freeFunc = freeFunc;
- reconfigure(info, rowBytes, ctable);
-}
-
-PixelRef::PixelRef(void* address, int fd, size_t mappedSize,
- const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
- : SkPixelRef(info)
- , mPixelStorageType(PixelStorageType::Ashmem) {
- mPixelStorage.ashmem.address = address;
- mPixelStorage.ashmem.fd = fd;
- mPixelStorage.ashmem.size = mappedSize;
- reconfigure(info, rowBytes, ctable);
-}
-
-PixelRef::~PixelRef() {
- switch (mPixelStorageType) {
- case PixelStorageType::External:
- mPixelStorage.external.freeFunc(mPixelStorage.external.address,
- mPixelStorage.external.context);
- break;
- case PixelStorageType::Ashmem:
- munmap(mPixelStorage.ashmem.address, mPixelStorage.ashmem.size);
- close(mPixelStorage.ashmem.fd);
- break;
- case PixelStorageType::Heap:
- free(mPixelStorage.heap.address);
- break;
- }
-
- if (android::uirenderer::Caches::hasInstance()) {
- android::uirenderer::Caches::getInstance().textureCache.releaseTexture(getStableID());
- }
-}
-
-bool PixelRef::hasHardwareMipMap() const {
- return mHasHardwareMipMap;
-}
-
-void PixelRef::setHasHardwareMipMap(bool hasMipMap) {
- mHasHardwareMipMap = hasMipMap;
-}
-
-void* PixelRef::getStorage() const {
- switch (mPixelStorageType) {
- case PixelStorageType::External:
- return mPixelStorage.external.address;
- case PixelStorageType::Ashmem:
- return mPixelStorage.ashmem.address;
- case PixelStorageType::Heap:
- return mPixelStorage.heap.address;
- }
-}
-
-bool PixelRef::onNewLockPixels(LockRec* rec) {
- rec->fPixels = getStorage();
- rec->fRowBytes = mRowBytes;
- rec->fColorTable = mColorTable.get();
- return true;
-}
-
-size_t PixelRef::getAllocatedSizeInBytes() const {
- return info().getSafeSize(mRowBytes);
-}
-
-int PixelRef::getAshmemFd() const {
- switch (mPixelStorageType) {
- case PixelStorageType::Ashmem:
- return mPixelStorage.ashmem.fd;
- default:
- return -1;
- }
-}
-
-size_t PixelRef::getAllocationByteCount() const {
- switch (mPixelStorageType) {
- case PixelStorageType::Heap:
- return mPixelStorage.heap.size;
- default:
- return rowBytes() * height();
- }
-}
-
-void PixelRef::reconfigure(const SkImageInfo& info) {
- reconfigure(info, info.minRowBytes(), nullptr);
-}
-
-void PixelRef::setAlphaType(SkAlphaType alphaType) {
- if (!SkColorTypeValidateAlphaType(info().colorType(), alphaType, &alphaType)) {
- return;
- }
-
- changeAlphaType(alphaType);
-}
-
-void PixelRef::getSkBitmap(SkBitmap* outBitmap) {
- outBitmap->setInfo(info(), rowBytes());
- outBitmap->setPixelRef(this);
- outBitmap->setHasHardwareMipMap(mHasHardwareMipMap);
-}
-
-} // namespace android
\ No newline at end of file
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index c2ed864..42da293 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -617,17 +617,17 @@
reinterpret_cast<intptr_t>( staticPostAndWait(task) ));
}
-CREATE_BRIDGE2(prepareToDraw, RenderThread* thread, SkBitmap* bitmap) {
+CREATE_BRIDGE2(prepareToDraw, RenderThread* thread, Bitmap* bitmap) {
if (Caches::hasInstance() && args->thread->eglManager().hasEglContext()) {
ATRACE_NAME("Bitmap#prepareToDraw task");
Caches::getInstance().textureCache.prefetch(args->bitmap);
}
- delete args->bitmap;
+ args->bitmap->unref();
args->bitmap = nullptr;
return nullptr;
}
-void RenderProxy::prepareToDraw(const SkBitmap& bitmap) {
+void RenderProxy::prepareToDraw(Bitmap& bitmap) {
// If we haven't spun up a hardware accelerated window yet, there's no
// point in precaching these bitmaps as it can't impact jank.
// We also don't know if we even will spin up a hardware-accelerated
@@ -636,7 +636,8 @@
RenderThread* renderThread = &RenderThread::getInstance();
SETUP_TASK(prepareToDraw);
args->thread = renderThread;
- args->bitmap = new SkBitmap(bitmap);
+ bitmap.ref();
+ args->bitmap = &bitmap;
nsecs_t lastVsync = renderThread->timeLord().latestVsync();
nsecs_t estimatedNextVsync = lastVsync + renderThread->timeLord().frameIntervalNanos();
nsecs_t timeToNextVsync = estimatedNextVsync - systemTime(CLOCK_MONOTONIC);
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 50a6f64..ae9330d 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -128,7 +128,7 @@
ANDROID_API static int copySurfaceInto(sp<Surface>& surface,
int left, int top, int right, int bottom, SkBitmap* bitmap);
- ANDROID_API static void prepareToDraw(const SkBitmap& bitmap);
+ ANDROID_API static void prepareToDraw(Bitmap& bitmap);
private:
RenderThread& mRenderThread;
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 51c0a05..cdaa705 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -21,6 +21,7 @@
#include <Matrix.h>
#include <Rect.h>
#include <RenderNode.h>
+#include <hwui/Bitmap.h>
#include <renderstate/RenderState.h>
#include <renderthread/RenderThread.h>
#include <Snapshot.h>
@@ -121,15 +122,16 @@
return snapshot;
}
- static SkBitmap createSkBitmap(int width, int height,
+ static sk_sp<Bitmap> createBitmap(int width, int height,
SkColorType colorType = kN32_SkColorType) {
- SkBitmap bitmap;
- sk_sp<SkColorSpace> colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
- SkImageInfo info = SkImageInfo::Make(width, height,
- colorType, kPremul_SkAlphaType, colorSpace);
- bitmap.setInfo(info);
- bitmap.allocPixels(info);
- return bitmap;
+ SkImageInfo info = SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType);
+ return Bitmap::allocateHeapBitmap(info);
+ }
+
+ static sk_sp<Bitmap> createBitmap(int width, int height, SkBitmap* outBitmap) {
+ SkImageInfo info = SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType);
+ outBitmap->setInfo(info);
+ return Bitmap::allocateHeapBitmap(outBitmap, nullptr);
}
static sp<DeferredLayerUpdater> createTextureLayerUpdater(
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index 24c3b23..c1144be 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -29,10 +29,11 @@
});
class ListViewAnimation : public TestListViewSceneBase {
- SkBitmap createRandomCharIcon(int cardHeight) {
+ sk_sp<Bitmap> createRandomCharIcon(int cardHeight) {
+ SkBitmap skBitmap;
int size = cardHeight - (dp(10) * 2);
- SkBitmap bitmap = TestUtils::createSkBitmap(size, size);
- SkCanvas canvas(bitmap);
+ sk_sp<Bitmap> bitmap(TestUtils::createBitmap(size, size, &skBitmap));
+ SkCanvas canvas(skBitmap);
canvas.clear(0);
SkPaint paint;
@@ -54,11 +55,12 @@
return bitmap;
}
- static SkBitmap createBoxBitmap(bool filled) {
+ static sk_sp<Bitmap> createBoxBitmap(bool filled) {
int size = dp(20);
int stroke = dp(2);
- SkBitmap bitmap = TestUtils::createSkBitmap(size, size);
- SkCanvas canvas(bitmap);
+ SkBitmap skBitmap;
+ auto bitmap = TestUtils::createBitmap(size, size, &skBitmap);
+ SkCanvas canvas(skBitmap);
canvas.clear(Color::Transparent);
SkPaint paint;
@@ -72,8 +74,8 @@
void createListItem(RenderProperties& props, Canvas& canvas, int cardId,
int itemWidth, int itemHeight) override {
- static SkBitmap filledBox = createBoxBitmap(true);
- static SkBitmap strokedBox = createBoxBitmap(false);
+ static sk_sp<Bitmap> filledBox(createBoxBitmap(true));
+ static sk_sp<Bitmap> strokedBox(createBoxBitmap(false));
// TODO: switch to using round rect clipping, once merging correctly handles that
SkPaint roundRectPaint;
roundRectPaint.setAntiAlias(true);
@@ -92,9 +94,10 @@
TestUtils::drawUtf8ToCanvas(&canvas, "This is some more text on the card", textPaint,
itemHeight, dp(45));
- canvas.drawBitmap(createRandomCharIcon(itemHeight), dp(10), dp(10), nullptr);
+ auto randomIcon = createRandomCharIcon(itemHeight);
+ canvas.drawBitmap(*randomIcon, dp(10), dp(10), nullptr);
- const SkBitmap& boxBitmap = rand() % 2 ? filledBox : strokedBox;
- canvas.drawBitmap(boxBitmap, itemWidth - dp(10) - boxBitmap.width(), dp(10), nullptr);
+ auto box = rand() % 2 ? filledBox : strokedBox;
+ canvas.drawBitmap(*box, itemWidth - dp(10) - box->width(), dp(10), nullptr);
}
};
diff --git a/libs/hwui/tests/common/scenes/RecentsAnimation.cpp b/libs/hwui/tests/common/scenes/RecentsAnimation.cpp
index 584f684..8256024 100644
--- a/libs/hwui/tests/common/scenes/RecentsAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/RecentsAnimation.cpp
@@ -45,12 +45,14 @@
int x = dp(32);
for (int i = 0; i < 4; i++) {
int y = (height / 4) * i;
- SkBitmap thumb = TestUtils::createSkBitmap(thumbnailSize, thumbnailSize);
- thumb.eraseColor(COLORS[i]);
- sp<RenderNode> card = createCard(x, y, cardsize, cardsize, thumb);
+ SkBitmap bitmap;
+ sk_sp<Bitmap> thumb(TestUtils::createBitmap(thumbnailSize, thumbnailSize, &bitmap));
+
+ bitmap.eraseColor(COLORS[i]);
+ sp<RenderNode> card = createCard(x, y, cardsize, cardsize, *thumb);
card->mutateStagingProperties().setElevation(i * dp(8));
renderer.drawRenderNode(card.get());
- mThumbnail = thumb;
+ mThumbnail = bitmap;
mCards.push_back(card);
}
@@ -68,8 +70,7 @@
}
private:
- sp<RenderNode> createCard(int x, int y, int width, int height,
- const SkBitmap& thumb) {
+ sp<RenderNode> createCard(int x, int y, int width, int height, Bitmap& thumb) {
return TestUtils::createNode(x, y, x + width, y + height,
[&thumb, width, height](RenderProperties& props, Canvas& canvas) {
props.setElevation(dp(16));
diff --git a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp
index 10cf05a..da724a9 100644
--- a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp
+++ b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp
@@ -93,7 +93,7 @@
delete canvas->finishRecording();
SkPaint rectPaint;
- SkBitmap iconBitmap = TestUtils::createSkBitmap(80, 80);
+ sk_sp<Bitmap> iconBitmap(TestUtils::createBitmap(80, 80));
while (benchState.KeepRunning()) {
canvas->resetRecording(100, 100);
@@ -105,7 +105,7 @@
{
canvas->save(SaveFlags::MatrixClip);
canvas->translate(10, 10);
- canvas->drawBitmap(iconBitmap, 0, 0, nullptr);
+ canvas->drawBitmap(*iconBitmap, 0, 0, nullptr);
canvas->restore();
}
benchmark::DoNotOptimize(canvas.get());
diff --git a/libs/hwui/tests/microbench/FrameBuilderBench.cpp b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
index 93aa574..d68f5bd 100644
--- a/libs/hwui/tests/microbench/FrameBuilderBench.cpp
+++ b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
@@ -41,7 +41,7 @@
static sp<RenderNode> createTestNode() {
auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
- SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
+ sk_sp<Bitmap> bitmap(TestUtils::createBitmap(10, 10));
SkPaint paint;
// Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
@@ -50,7 +50,7 @@
for (int i = 0; i < 30; i++) {
canvas.translate(0, 10);
canvas.drawRect(0, 0, 10, 10, paint);
- canvas.drawBitmap(bitmap, 5, 0, nullptr);
+ canvas.drawBitmap(*bitmap, 5, 0, nullptr);
}
canvas.restore();
});
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index 818c29c..01046e1 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -129,9 +129,9 @@
auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
- SkBitmap bitmap = TestUtils::createSkBitmap(25, 25);
+ sk_sp<Bitmap> bitmap(TestUtils::createBitmap(25, 25));
canvas.drawRect(0, 0, 100, 200, SkPaint());
- canvas.drawBitmap(bitmap, 10, 10, nullptr);
+ canvas.drawBitmap(*bitmap, 10, 10, nullptr);
});
FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200,
sLightGeometry, Caches::getInstance());
@@ -200,8 +200,9 @@
auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
- SkBitmap bitmap = TestUtils::createSkBitmap(10, 10,
- kAlpha_8_SkColorType); // Disable merging by using alpha 8 bitmap
+
+ sk_sp<Bitmap> bitmap(TestUtils::createBitmap(10, 10,
+ kAlpha_8_SkColorType)); // Disable merging by using alpha 8 bitmap
// Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
// Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
@@ -209,7 +210,7 @@
for (int i = 0; i < LOOPS; i++) {
canvas.translate(0, 10);
canvas.drawRect(0, 0, 10, 10, SkPaint());
- canvas.drawBitmap(bitmap, 5, 0, nullptr);
+ canvas.drawBitmap(*bitmap, 5, 0, nullptr);
}
canvas.restore();
});
@@ -393,19 +394,19 @@
}
RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_bitmaps) {
- static SkBitmap opaqueBitmap = TestUtils::createSkBitmap(50, 50,
- SkColorType::kRGB_565_SkColorType);
- static SkBitmap transpBitmap = TestUtils::createSkBitmap(50, 50,
- SkColorType::kAlpha_8_SkColorType);
+ static sk_sp<Bitmap> opaqueBitmap(TestUtils::createBitmap(50, 50,
+ SkColorType::kRGB_565_SkColorType));
+ static sk_sp<Bitmap> transpBitmap(TestUtils::createBitmap(50, 50,
+ SkColorType::kAlpha_8_SkColorType));
class AvoidOverdrawBitmapsTestRenderer : public TestRendererBase {
public:
void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
switch(mIndex++) {
case 0:
- EXPECT_EQ(opaqueBitmap.pixelRef(), op.bitmap->pixelRef());
+ EXPECT_EQ(opaqueBitmap.get(), op.bitmap);
break;
case 1:
- EXPECT_EQ(transpBitmap.pixelRef(), op.bitmap->pixelRef());
+ EXPECT_EQ(transpBitmap.get(), op.bitmap);
break;
default:
ADD_FAILURE() << "Only two ops expected.";
@@ -417,11 +418,11 @@
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.drawRect(0, 0, 50, 50, SkPaint());
canvas.drawRect(0, 0, 50, 50, SkPaint());
- canvas.drawBitmap(transpBitmap, 0, 0, nullptr);
+ canvas.drawBitmap(*transpBitmap, 0, 0, nullptr);
// only the below draws should remain, since they're
- canvas.drawBitmap(opaqueBitmap, 0, 0, nullptr);
- canvas.drawBitmap(transpBitmap, 0, 0, nullptr);
+ canvas.drawBitmap(*opaqueBitmap, 0, 0, nullptr);
+ canvas.drawBitmap(*transpBitmap, 0, 0, nullptr);
});
FrameBuilder frameBuilder(SkRect::MakeWH(50, 50), 50, 50,
sLightGeometry, Caches::getInstance());
@@ -449,23 +450,23 @@
};
auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
- SkBitmap bitmap = TestUtils::createSkBitmap(20, 20);
+ sk_sp<Bitmap> bitmap(TestUtils::createBitmap(20, 20));
// left side clipped (to inset left half)
canvas.clipRect(10, 0, 50, 100, SkRegion::kReplace_Op);
- canvas.drawBitmap(bitmap, 0, 40, nullptr);
+ canvas.drawBitmap(*bitmap, 0, 40, nullptr);
// top side clipped (to inset top half)
canvas.clipRect(0, 10, 100, 50, SkRegion::kReplace_Op);
- canvas.drawBitmap(bitmap, 40, 0, nullptr);
+ canvas.drawBitmap(*bitmap, 40, 0, nullptr);
// right side clipped (to inset right half)
canvas.clipRect(50, 0, 90, 100, SkRegion::kReplace_Op);
- canvas.drawBitmap(bitmap, 80, 40, nullptr);
+ canvas.drawBitmap(*bitmap, 80, 40, nullptr);
// bottom not clipped, just abutting (inset bottom half)
canvas.clipRect(0, 50, 100, 90, SkRegion::kReplace_Op);
- canvas.drawBitmap(bitmap, 40, 70, nullptr);
+ canvas.drawBitmap(*bitmap, 40, 70, nullptr);
});
FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
@@ -822,8 +823,8 @@
auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
- SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
- canvas.drawBitmap(bitmap, 0, 0, nullptr);
+ sk_sp<Bitmap> bitmap(TestUtils::createBitmap(200, 200));
+ canvas.drawBitmap(*bitmap, 0, 0, nullptr);
});
// clip to small area, should see in receiver
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index 46e685c..134497c 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -261,8 +261,7 @@
TEST(RecordingCanvas, backgroundAndImage) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
- SkBitmap bitmap;
- bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25));
+ sk_sp<Bitmap> bitmap(TestUtils::createBitmap(25, 25));
SkPaint paint;
paint.setColor(SK_ColorBLUE);
@@ -278,7 +277,7 @@
canvas.save(SaveFlags::MatrixClip);
canvas.translate(25, 25);
canvas.scale(2, 2);
- canvas.drawBitmap(bitmap, 0, 0, nullptr);
+ canvas.drawBitmap(*bitmap, 0, 0, nullptr);
canvas.restore();
}
canvas.restore();
@@ -728,19 +727,21 @@
}
TEST(RecordingCanvas, refBitmap) {
- SkBitmap bitmap = TestUtils::createSkBitmap(100, 100);
+ sk_sp<Bitmap> bitmap(TestUtils::createBitmap(100, 100));
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
- canvas.drawBitmap(bitmap, 0, 0, nullptr);
+ canvas.drawBitmap(*bitmap, 0, 0, nullptr);
});
auto& bitmaps = dl->getBitmapResources();
EXPECT_EQ(1u, bitmaps.size());
}
TEST(RecordingCanvas, refBitmapInShader_bitmapShader) {
- SkBitmap bitmap = TestUtils::createSkBitmap(100, 100);
+ sk_sp<Bitmap> bitmap = TestUtils::createBitmap(100, 100);
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
SkPaint paint;
- sk_sp<SkShader> shader = SkMakeBitmapShader(bitmap,
+ SkBitmap skBitmap;
+ bitmap->getSkBitmap(&skBitmap);
+ sk_sp<SkShader> shader = SkMakeBitmapShader(skBitmap,
SkShader::TileMode::kClamp_TileMode,
SkShader::TileMode::kClamp_TileMode,
nullptr,
@@ -754,10 +755,12 @@
}
TEST(RecordingCanvas, refBitmapInShader_composeShader) {
- SkBitmap bitmap = TestUtils::createSkBitmap(100, 100);
+ sk_sp<Bitmap> bitmap = TestUtils::createBitmap(100, 100);
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
SkPaint paint;
- sk_sp<SkShader> shader1 = SkMakeBitmapShader(bitmap,
+ SkBitmap skBitmap;
+ bitmap->getSkBitmap(&skBitmap);
+ sk_sp<SkShader> shader1 = SkMakeBitmapShader(skBitmap,
SkShader::TileMode::kClamp_TileMode,
SkShader::TileMode::kClamp_TileMode,
nullptr,
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index 1f4788a..49c4da6e 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -25,12 +25,20 @@
using namespace android;
using namespace android::uirenderer;
+SkBitmap createSkBitmap(int width, int height) {
+ SkBitmap bitmap;
+ SkImageInfo info = SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType);
+ bitmap.setInfo(info);
+ bitmap.allocPixels(info);
+ return bitmap;
+}
+
/**
* 1x1 bitmaps must not be optimized into solid color shaders, since HWUI can't
* compose/render color shaders
*/
TEST(SkiaBehavior, CreateBitmapShader1x1) {
- SkBitmap origBitmap = TestUtils::createSkBitmap(1, 1);
+ SkBitmap origBitmap = createSkBitmap(1, 1);
sk_sp<SkShader> s = SkMakeBitmapShader(
origBitmap,
SkShader::kClamp_TileMode,
@@ -49,7 +57,7 @@
}
TEST(SkiaBehavior, genIds) {
- SkBitmap bitmap = TestUtils::createSkBitmap(100, 100);
+ SkBitmap bitmap = createSkBitmap(100, 100);
uint32_t genId = bitmap.getGenerationID();
bitmap.notifyPixelsChanged();
EXPECT_NE(genId, bitmap.getGenerationID());
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 2b3ed87..da0e515 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -1465,7 +1465,7 @@
mGpsNmeaListener = null;
mNmeaBuffer = null;
mOldGnssCallback = null;
- mGnssCallback = new GnssStatus.Callback() {
+ mGnssCallback = mGpsListener != null ? new GnssStatus.Callback() {
@Override
public void onStarted() {
mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STARTED);
@@ -1485,7 +1485,7 @@
public void onSatelliteStatusChanged(GnssStatus status) {
mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_SATELLITE_STATUS);
}
- };
+ } : null;
mOldGnssNmeaListener = null;
mGnssNmeaListener = null;
}
@@ -1502,12 +1502,12 @@
mOldGnssCallback = null;
mGnssCallback = null;
mOldGnssNmeaListener = null;
- mGnssNmeaListener = new OnNmeaMessageListener() {
+ mGnssNmeaListener = mGpsNmeaListener != null ? new OnNmeaMessageListener() {
@Override
public void onNmeaMessage(String nmea, long timestamp) {
mGpsNmeaListener.onNmeaReceived(timestamp, nmea);
}
- };
+ } : null;
}
GnssStatusListenerTransport(GnssStatusCallback callback) {
@@ -1516,7 +1516,7 @@
GnssStatusListenerTransport(GnssStatusCallback callback, Handler handler) {
mOldGnssCallback = callback;
- mGnssCallback = new GnssStatus.Callback() {
+ mGnssCallback = mOldGnssCallback != null ? new GnssStatus.Callback() {
@Override
public void onStarted() {
mOldGnssCallback.onStarted();
@@ -1536,7 +1536,7 @@
public void onSatelliteStatusChanged(GnssStatus status) {
mOldGnssCallback.onSatelliteStatusChanged(status);
}
- };
+ } : null;
mGnssHandler = new GnssHandler(handler);
mOldGnssNmeaListener = null;
mGnssNmeaListener = null;
@@ -1569,12 +1569,12 @@
mOldGnssCallback = null;
mGnssHandler = new GnssHandler(handler);
mOldGnssNmeaListener = listener;
- mGnssNmeaListener = new OnNmeaMessageListener() {
+ mGnssNmeaListener = mOldGnssNmeaListener != null ? new OnNmeaMessageListener() {
@Override
public void onNmeaMessage(String message, long timestamp) {
mOldGnssNmeaListener.onNmeaReceived(timestamp, message);
}
- };
+ } : null;
mGpsListener = null;
mGpsNmeaListener = null;
mNmeaBuffer = new ArrayList<Nmea>();
@@ -1597,7 +1597,7 @@
@Override
public void onGnssStarted() {
- if (mGpsListener != null) {
+ if (mGnssCallback != null) {
Message msg = Message.obtain();
msg.what = GpsStatus.GPS_EVENT_STARTED;
mGnssHandler.sendMessage(msg);
@@ -1606,7 +1606,7 @@
@Override
public void onGnssStopped() {
- if (mGpsListener != null) {
+ if (mGnssCallback != null) {
Message msg = Message.obtain();
msg.what = GpsStatus.GPS_EVENT_STOPPED;
mGnssHandler.sendMessage(msg);
@@ -1615,7 +1615,7 @@
@Override
public void onFirstFix(int ttff) {
- if (mGpsListener != null) {
+ if (mGnssCallback != null) {
mTimeToFirstFix = ttff;
Message msg = Message.obtain();
msg.what = GpsStatus.GPS_EVENT_FIRST_FIX;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index bbc249f..4f2fdff 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -257,6 +257,16 @@
/*
* (non-Javadoc)
+ * @see android.hardware.camera2.ICameraDeviceCallbacks#onRequestQueueEmpty()
+ */
+ @Override
+ public void onRequestQueueEmpty() throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ /*
+ * (non-Javadoc)
* @see android.hardware.camera2.ICameraDeviceCallbacks#onRepeatingRequestError()
*/
@Override
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 6c879b9..832363c 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -148,6 +148,16 @@
/*
* (non-Javadoc)
+ * @see android.hardware.camera2.ICameraDeviceCallbacks#onRequestQueueEmpty()
+ */
+ @Override
+ public void onRequestQueueEmpty() throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ /*
+ * (non-Javadoc)
* @see android.hardware.camera2.ICameraDeviceCallbacks#onRepeatingRequestError()
*/
@Override
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index b58c87a..bb8eb2c 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -115,6 +115,7 @@
myWebView.clearCache(true);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
+ webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
mWebViewClient = new MyWebViewClient();
myWebView.setWebViewClient(mWebViewClient);
myWebView.setWebChromeClient(new MyWebChromeClient());
diff --git a/packages/ExtServices/src/android/ext/services/notification/Ranker.java b/packages/ExtServices/src/android/ext/services/notification/Ranker.java
index 2ce667c..63fc157 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Ranker.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Ranker.java
@@ -35,180 +35,15 @@
import android.ext.services.R;
/**
- * Class that provides an updatable ranker module for the notification manager..
+ * Class that provides an updatable ranker module for the notification manager.
*/
public final class Ranker extends NotificationRankerService {
private static final String TAG = "RocketRanker";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final int AUTOBUNDLE_AT_COUNT = 4;
- private static final String AUTOBUNDLE_KEY = "ranker_bundle";
-
- // Map of user : <Map of package : notification keys>. Only contains notifications that are not
- // bundled by the app (aka no group or sort key).
- Map<Integer, Map<String, LinkedHashSet<String>>> mUnbundledNotifications;
-
@Override
public Adjustment onNotificationEnqueued(StatusBarNotification sbn, int importance,
boolean user) {
- if (DEBUG) Log.i(TAG, "ENQUEUED " + sbn.getKey());
return null;
}
-
- @Override
- public void onNotificationPosted(StatusBarNotification sbn) {
- if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey());
- try {
- List<String> notificationsToBundle = new ArrayList<>();
- if (!sbn.isAppGroup()) {
- // Not grouped by the app, add to the list of notifications for the app;
- // send bundling update if app exceeds the autobundling limit.
- synchronized (mUnbundledNotifications) {
- Map<String, LinkedHashSet<String>> unbundledNotificationsByUser
- = mUnbundledNotifications.get(sbn.getUserId());
- if (unbundledNotificationsByUser == null) {
- unbundledNotificationsByUser = new HashMap<>();
- }
- mUnbundledNotifications.put(sbn.getUserId(), unbundledNotificationsByUser);
- LinkedHashSet<String> notificationsForPackage
- = unbundledNotificationsByUser.get(sbn.getPackageName());
- if (notificationsForPackage == null) {
- notificationsForPackage = new LinkedHashSet<>();
- }
-
- notificationsForPackage.add(sbn.getKey());
- unbundledNotificationsByUser.put(sbn.getPackageName(), notificationsForPackage);
-
- if (notificationsForPackage.size() >= AUTOBUNDLE_AT_COUNT) {
- for (String key : notificationsForPackage) {
- notificationsToBundle.add(key);
- }
- }
- }
- if (notificationsToBundle.size() > 0) {
- adjustAutobundlingSummary(sbn.getPackageName(), notificationsToBundle.get(0),
- true, sbn.getUserId());
- adjustNotificationBundling(sbn.getPackageName(), notificationsToBundle, true,
- sbn.getUserId());
- }
- } else {
- // Grouped, but not by us. Send updates to unautobundle, if we bundled it.
- maybeUnbundle(sbn, false, sbn.getUserId());
- }
- } catch (Exception e) {
- Slog.e(TAG, "Failure processing new notification", e);
- }
- }
-
- @Override
- public void onNotificationRemoved(StatusBarNotification sbn) {
- try {
- maybeUnbundle(sbn, true, sbn.getUserId());
- } catch (Exception e) {
- Slog.e(TAG, "Error processing canceled notification", e);
- }
- }
-
- /**
- * Un-autobundles notifications that are now grouped by the app. Additionally cancels
- * autobundling if the status change of this notification resulted in the loose notification
- * count being under the limit.
- */
- private void maybeUnbundle(StatusBarNotification sbn, boolean notificationGone, int user) {
- List<String> notificationsToUnAutobundle = new ArrayList<>();
- boolean removeSummary = false;
- synchronized (mUnbundledNotifications) {
- Map<String, LinkedHashSet<String>> unbundledNotificationsByUser
- = mUnbundledNotifications.get(sbn.getUserId());
- if (unbundledNotificationsByUser == null || unbundledNotificationsByUser.size() == 0) {
- return;
- }
- LinkedHashSet<String> notificationsForPackage
- = unbundledNotificationsByUser.get(sbn.getPackageName());
- if (notificationsForPackage == null || notificationsForPackage.size() == 0) {
- return;
- }
- if (notificationsForPackage.remove(sbn.getKey())) {
- if (!notificationGone) {
- // Add the current notification to the unbundling list if it still exists.
- notificationsToUnAutobundle.add(sbn.getKey());
- }
- // If the status change of this notification has brought the number of loose
- // notifications back below the limit, remove the summary and un-autobundle.
- if (notificationsForPackage.size() == AUTOBUNDLE_AT_COUNT - 1) {
- removeSummary = true;
- for (String key : notificationsForPackage) {
- notificationsToUnAutobundle.add(key);
- }
- }
- }
- }
- if (notificationsToUnAutobundle.size() > 0) {
- if (removeSummary) {
- adjustAutobundlingSummary(sbn.getPackageName(), null, false, user);
- }
- adjustNotificationBundling(sbn.getPackageName(), notificationsToUnAutobundle, false,
- user);
- }
- }
-
- @Override
- public void onListenerConnected() {
- if (DEBUG) Log.i(TAG, "CONNECTED");
- mUnbundledNotifications = new HashMap<>();
- for (StatusBarNotification sbn : getActiveNotifications()) {
- onNotificationPosted(sbn);
- }
- }
-
- private void adjustAutobundlingSummary(String packageName, String key, boolean summaryNeeded,
- int user) {
- Bundle signals = new Bundle();
- if (summaryNeeded) {
- signals.putBoolean(Adjustment.NEEDS_AUTOGROUPING_KEY, true);
- signals.putString(Adjustment.GROUP_KEY_OVERRIDE_KEY, AUTOBUNDLE_KEY);
- } else {
- signals.putBoolean(Adjustment.NEEDS_AUTOGROUPING_KEY, false);
- }
- Adjustment adjustment = new Adjustment(packageName, key, IMPORTANCE_UNSPECIFIED, signals,
- getContext().getString(R.string.notification_ranker_autobundle_explanation), null,
- user);
- if (DEBUG) {
- Log.i(TAG, "Summary update for: " + packageName + " "
- + (summaryNeeded ? "adding" : "removing"));
- }
- try {
- adjustNotification(adjustment);
- } catch (Exception e) {
- Slog.e(TAG, "Adjustment failed", e);
- }
-
- }
- private void adjustNotificationBundling(String packageName, List<String> keys, boolean bundle,
- int user) {
- List<Adjustment> adjustments = new ArrayList<>();
- for (String key : keys) {
- adjustments.add(createBundlingAdjustment(packageName, key, bundle, user));
- if (DEBUG) Log.i(TAG, "Sending bundling adjustment for: " + key);
- }
- try {
- adjustNotifications(adjustments);
- } catch (Exception e) {
- Slog.e(TAG, "Adjustments failed", e);
- }
- }
-
- private Adjustment createBundlingAdjustment(String packageName, String key, boolean bundle,
- int user) {
- Bundle signals = new Bundle();
- if (bundle) {
- signals.putString(Adjustment.GROUP_KEY_OVERRIDE_KEY, AUTOBUNDLE_KEY);
- } else {
- signals.putString(Adjustment.GROUP_KEY_OVERRIDE_KEY, null);
- }
- return new Adjustment(packageName, key, IMPORTANCE_UNSPECIFIED, signals,
- getContext().getString(R.string.notification_ranker_autobundle_explanation),
- null, user);
- }
-
}
\ No newline at end of file
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
index caeb74c..d19821f 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
@@ -41,21 +41,12 @@
private static final long ANNOUNCEMENT_DELAY = 250;
private static final int DEFAULT_COLOR = -1;
- private final KeyguardUpdateMonitor mUpdateMonitor;
private final Handler mHandler;
private final int mDefaultColor;
- CharSequence mMessage;
+ private CharSequence mMessage;
private int mNextMessageColor = DEFAULT_COLOR;
- private final Runnable mClearMessageRunnable = new Runnable() {
- @Override
- public void run() {
- mMessage = null;
- update();
- }
- };
-
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
public void onFinishedGoingToSleep(int why) {
setSelected(false);
@@ -70,11 +61,14 @@
}
public KeyguardMessageArea(Context context, AttributeSet attrs) {
+ this(context, attrs, KeyguardUpdateMonitor.getInstance(context));
+ }
+
+ public KeyguardMessageArea(Context context, AttributeSet attrs, KeyguardUpdateMonitor monitor) {
super(context, attrs);
setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
- mUpdateMonitor = KeyguardUpdateMonitor.getInstance(getContext());
- mUpdateMonitor.registerCallback(mInfoCallback);
+ monitor.registerCallback(mInfoCallback);
mHandler = new Handler(Looper.myLooper());
mDefaultColor = getCurrentTextColor();
@@ -137,8 +131,8 @@
}
private void clearMessage() {
- mHandler.removeCallbacks(mClearMessageRunnable);
- mHandler.post(mClearMessageRunnable);
+ mMessage = null;
+ update();
}
private void update() {
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index a6d0cba..1f192c1 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -245,7 +245,7 @@
<string name="animator_duration_scale_title" msgid="3406722410819934083">"Длительность анимации"</string>
<string name="overlay_display_devices_title" msgid="5364176287998398539">"Эмуляция доп. экранов"</string>
<string name="debug_applications_category" msgid="4206913653849771549">"Приложения"</string>
- <string name="immediately_destroy_activities" msgid="1579659389568133959">"Не сохранять действия"</string>
+ <string name="immediately_destroy_activities" msgid="1579659389568133959">"Не сохранять активности"</string>
<string name="immediately_destroy_activities_summary" msgid="3592221124808773368">"Удалять сводку действий после их завершения"</string>
<string name="app_process_limit_title" msgid="4280600650253107163">"Лимит фоновых процессов"</string>
<string name="show_all_anrs" msgid="28462979638729082">"Все ANR"</string>
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index e2e721c..0aa76a0 100755
--- a/packages/SettingsLib/res/values/config.xml
+++ b/packages/SettingsLib/res/values/config.xml
@@ -23,8 +23,8 @@
<!-- Default data warning level in mb -->
<integer name="default_data_warning_level_mb">2048</integer>
- <!-- Whether to send a custom package name with the PSD. translatable="false"-->
- <bool name="config_sendPackageName">true</bool>
+ <!-- Whether to send a custom package name with the PSD.-->
+ <bool name="config_sendPackageName">false</bool>
<!-- Name for the set of keys associating package names -->
<string name="config_helpPackageNameKey" translatable="false"></string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index 4ec4f4f..c1f5660 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -24,6 +24,8 @@
public static final String CATEGORY_NETWORK = "com.android.settings.category.ia.wireless";
public static final String CATEGORY_DEVICE = "com.android.settings.category.ia.device";
public static final String CATEGORY_APPS = "com.android.settings.category.ia.apps";
+ public static final String CATEGORY_APPS_DEFAULT =
+ "com.android.settings.category.ia.apps.default";
public static final String CATEGORY_BATTERY = "com.android.settings.category.ia.battery";
public static final String CATEGORY_DISPLAY = "com.android.settings.category.ia.display";
public static final String CATEGORY_SOUND = "com.android.settings.category.ia.sound";
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index b2ce13f..ac10ca8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -142,9 +142,9 @@
// Only add Settings for this user.
getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true);
getTilesForAction(context, user, OPERATOR_SETTINGS, cache,
- OPERATOR_DEFAULT_CATEGORY, tiles, false);
+ OPERATOR_DEFAULT_CATEGORY, tiles, false, true);
getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
- MANUFACTURER_DEFAULT_CATEGORY, tiles, false);
+ MANUFACTURER_DEFAULT_CATEGORY, tiles, false, true);
}
if (setup) {
getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
@@ -211,12 +211,20 @@
private static void getTilesForAction(Context context,
UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings) {
+ getTilesForAction(context, user, action, addedCache, defaultCategory, outTiles,
+ requireSettings, requireSettings);
+ }
+
+ private static void getTilesForAction(Context context,
+ UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
+ String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings,
+ boolean usePriority) {
Intent intent = new Intent(action);
if (requireSettings) {
intent.setPackage(SETTING_PKG);
}
getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles,
- requireSettings, true);
+ usePriority, true);
}
public static void getTilesForIntent(Context context, UserHandle user, Intent intent,
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 672f88d..bb85de2 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -69,6 +69,7 @@
<integer name="def_power_sounds_enabled">1</integer>
<string name="def_low_battery_sound" translatable="false">/system/media/audio/ui/LowBattery.ogg</string>
<integer name="def_dock_sounds_enabled">0</integer>
+ <integer name="def_dock_sounds_enabled_when_accessibility">0</integer>
<string name="def_desk_dock_sound" translatable="false">/system/media/audio/ui/Dock.ogg</string>
<string name="def_desk_undock_sound" translatable="false">/system/media/audio/ui/Undock.ogg</string>
<string name="def_car_dock_sound" translatable="false">/system/media/audio/ui/Dock.ogg</string>
@@ -222,4 +223,7 @@
<!-- Default setting for ability to add users from the lock screen -->
<bool name="def_add_users_from_lockscreen">false</bool>
+
+ <!-- default setting for Settings.System.END_BUTTON_BEHAVIOR : END_BUTTON_BEHAVIOR_SLEEP -->
+ <integer name="def_end_button_behavior">0x2</integer>
</resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index c1a1f84..d55bb4f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -2662,6 +2662,8 @@
R.string.def_low_battery_sound);
loadIntegerSetting(stmt, Settings.Global.DOCK_SOUNDS_ENABLED,
R.integer.def_dock_sounds_enabled);
+ loadIntegerSetting(stmt, Settings.Global.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY,
+ R.integer.def_dock_sounds_enabled_when_accessibility);
loadStringSetting(stmt, Settings.Global.DESK_DOCK_SOUND,
R.string.def_desk_dock_sound);
loadStringSetting(stmt, Settings.Global.DESK_UNDOCK_SOUND,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index afc524c..e7f5f4f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2137,7 +2137,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 132;
+ private static final int SETTINGS_VERSION = 134;
private final int mUserId;
@@ -2452,6 +2452,21 @@
}
if (currentVersion == 130) {
+ // Split Ambient settings
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ boolean dozeExplicitlyDisabled = "0".equals(secureSettings.
+ getSettingLocked(Settings.Secure.DOZE_ENABLED).getValue());
+
+ if (dozeExplicitlyDisabled) {
+ secureSettings.insertSettingLocked(Settings.Secure.DOZE_PULSE_ON_PICK_UP,
+ "0", SettingsState.SYSTEM_PACKAGE_NAME);
+ secureSettings.insertSettingLocked(Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP,
+ "0", SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ currentVersion = 131;
+ }
+
+ if (currentVersion == 131) {
// Initialize new multi-press timeout to default value
final SettingsState systemSecureSettings = getSecureSettingsLocked(userId);
final String oldValue = systemSecureSettings.getSettingLocked(
@@ -2464,11 +2479,11 @@
SettingsState.SYSTEM_PACKAGE_NAME);
}
- currentVersion = 131;
+ currentVersion = 132;
}
- if (currentVersion == 131) {
- // Version 131: Allow managed profile to optionally use the parent's ringtones
+ if (currentVersion == 132) {
+ // Version 132: Allow managed profile to optionally use the parent's ringtones
final SettingsState systemSecureSettings = getSecureSettingsLocked(userId);
String defaultSyncParentSounds = (getContext().getResources()
.getBoolean(R.bool.def_sync_parent_sounds) ? "1" : "0");
@@ -2476,7 +2491,20 @@
Settings.Secure.SYNC_PARENT_SOUNDS,
defaultSyncParentSounds,
SettingsState.SYSTEM_PACKAGE_NAME);
- currentVersion = 132;
+ currentVersion = 133;
+ }
+
+ if (currentVersion == 133) {
+ // Version 133: Add default end button behavior
+ final SettingsState systemSettings = getSystemSettingsLocked(userId);
+ if (systemSettings.getSettingLocked(Settings.System.END_BUTTON_BEHAVIOR) ==
+ null) {
+ String defaultEndButtonBehavior = Integer.toString(getContext()
+ .getResources().getInteger(R.integer.def_end_button_behavior));
+ systemSettings.insertSettingLocked(Settings.System.END_BUTTON_BEHAVIOR,
+ defaultEndButtonBehavior, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ currentVersion = 134;
}
if (currentVersion != newVersion) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 02518f2..4d59d57 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -389,7 +389,7 @@
<!-- started from PipUI -->
<activity
- android:name="com.android.systemui.tv.pip.PipMenuActivity"
+ android:name=".pip.tv.PipMenuActivity"
android:exported="true"
android:theme="@style/PipTheme"
android:launchMode="singleTop"
@@ -400,7 +400,7 @@
androidprv:alwaysFocusable="true"
android:excludeFromRecents="true" />
<activity
- android:name="com.android.systemui.tv.pip.PipOverlayActivity"
+ android:name=".pip.tv.PipOverlayActivity"
android:exported="true"
android:theme="@style/PipTheme"
android:taskAffinity=""
@@ -409,7 +409,7 @@
android:supportsPictureInPicture="true"
android:excludeFromRecents="true" />
<activity
- android:name="com.android.systemui.tv.pip.PipOnboardingActivity"
+ android:name=".pip.tv.PipOnboardingActivity"
android:exported="true"
android:theme="@style/PipTheme"
android:launchMode="singleTop"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java
new file mode 100644
index 0000000..0728482
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 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.plugins.statusbar.phone;
+
+import android.view.MotionEvent;
+
+import com.android.systemui.plugins.Plugin;
+
+public interface NavGesture extends Plugin {
+
+ public static final String ACTION = "com.android.systemui.action.PLUGIN_NAV_GESTURE";
+
+ public static final int VERSION = 1;
+
+ public GestureHelper getGestureHelper();
+
+ public interface GestureHelper {
+ public boolean onTouchEvent(MotionEvent event);
+
+ public boolean onInterceptTouchEvent(MotionEvent event);
+
+ public void setBarState(boolean vertical, boolean isRtl);
+ }
+
+}
diff --git a/packages/SystemUI/res/drawable/pip_dismiss.xml b/packages/SystemUI/res/drawable/pip_dismiss.xml
new file mode 100644
index 0000000..f656eeb
--- /dev/null
+++ b/packages/SystemUI/res/drawable/pip_dismiss.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2016 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="42.0dp"
+ android:height="42.0dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M38.000000,12.800000l-2.799999,-2.800000 -11.200001,11.200001 -11.200000,-11.200001 -2.800000,2.800000 11.200001,11.200000 -11.200001,11.200001 2.800000,2.799999 11.200000,-11.200001 11.200001,11.200001 2.799999,-2.799999 -11.200001,-11.200001z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pip_dismiss_background.xml b/packages/SystemUI/res/drawable/pip_dismiss_background.xml
new file mode 100644
index 0000000..3a75296
--- /dev/null
+++ b/packages/SystemUI/res/drawable/pip_dismiss_background.xml
@@ -0,0 +1,22 @@
+<!--
+Copyright (C) 2016 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <corners
+ android:radius="100dp" />
+ <solid
+ android:color="#66000000" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/pip_dismiss_view.xml b/packages/SystemUI/res/layout/pip_dismiss_view.xml
new file mode 100644
index 0000000..141e610
--- /dev/null
+++ b/packages/SystemUI/res/layout/pip_dismiss_view.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pip_dismiss_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/pip_dismiss_background"
+ android:foreground="@drawable/pip_dismiss"
+ android:alpha="0"
+ android:forceHasOverlappingRendering="false" />
diff --git a/packages/SystemUI/res/layout/tv_pip_control_button.xml b/packages/SystemUI/res/layout/tv_pip_control_button.xml
index 096dda8..b9b0154 100644
--- a/packages/SystemUI/res/layout/tv_pip_control_button.xml
+++ b/packages/SystemUI/res/layout/tv_pip_control_button.xml
@@ -17,7 +17,7 @@
*/
-->
-<!-- Layout for {@link com.android.systemui.tv.pip.PipControlButtonView}. -->
+<!-- Layout for {@link com.android.systemui.pip.tv.PipControlButtonView}. -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView android:id="@+id/button"
diff --git a/packages/SystemUI/res/layout/tv_pip_controls.xml b/packages/SystemUI/res/layout/tv_pip_controls.xml
index 49119fb..c6bcd32 100644
--- a/packages/SystemUI/res/layout/tv_pip_controls.xml
+++ b/packages/SystemUI/res/layout/tv_pip_controls.xml
@@ -17,17 +17,17 @@
*/
-->
-<!-- Layout for {@link com.android.systemui.tv.pip.PipControlsView}. -->
+<!-- Layout for {@link com.android.systemui.pip.tv.PipControlsView}. -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <com.android.systemui.tv.pip.PipControlButtonView
+ <com.android.systemui.pip.tv.PipControlButtonView
android:id="@+id/full_button"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:src="@drawable/ic_fullscreen_white_24dp"
android:text="@string/pip_fullscreen" />
- <com.android.systemui.tv.pip.PipControlButtonView
+ <com.android.systemui.pip.tv.PipControlButtonView
android:id="@+id/close_button"
android:layout_width="100dp"
android:layout_height="wrap_content"
@@ -35,7 +35,7 @@
android:src="@drawable/ic_close_white"
android:text="@string/pip_close" />
- <com.android.systemui.tv.pip.PipControlButtonView
+ <com.android.systemui.pip.tv.PipControlButtonView
android:id="@+id/play_pause_button"
android:layout_width="100dp"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/tv_pip_menu.xml b/packages/SystemUI/res/layout/tv_pip_menu.xml
index 72a4929..35f2af4 100644
--- a/packages/SystemUI/res/layout/tv_pip_menu.xml
+++ b/packages/SystemUI/res/layout/tv_pip_menu.xml
@@ -26,7 +26,7 @@
android:gravity="top|center_horizontal"
android:clipChildren="false">
- <com.android.systemui.tv.pip.PipControlsView
+ <com.android.systemui.pip.tv.PipControlsView
android:id="@+id/pip_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/tv_pip_recents_overlay.xml b/packages/SystemUI/res/layout/tv_pip_recents_overlay.xml
index f157fd5..949400c 100644
--- a/packages/SystemUI/res/layout/tv_pip_recents_overlay.xml
+++ b/packages/SystemUI/res/layout/tv_pip_recents_overlay.xml
@@ -20,7 +20,7 @@
android:gravity="top|center_horizontal"
android:orientation="vertical">
- <com.android.systemui.tv.pip.PipRecentsControlsView
+ <com.android.systemui.pip.tv.PipRecentsControlsView
android:id="@+id/pip_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -35,13 +35,13 @@
android:layout_gravity="top|center_horizontal"
android:background="@drawable/tv_pip_recents_overlay_scrim"
android:alpha="0" />
- <com.android.systemui.tv.pip.PipControlsView
+ <com.android.systemui.pip.tv.PipControlsView
android:id="@+id/pip_control_contents"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_gravity="top|center_horizontal" />
- </com.android.systemui.tv.pip.PipRecentsControlsView>
+ </com.android.systemui.pip.tv.PipRecentsControlsView>
<!-- Placeholder view to handle focus change between Recents row and PIP controls
in talkback mode -->
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index b8be190..1131b2a 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Blaaier"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontakte"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-pos"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Musiek"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalender"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 6cbb5c6..0dc05e6 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"አሳሽ"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"እውቂያዎች"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"ኢሜይል"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"ኤስኤምኤስ"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"ሙዚቃ"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"የቀን መቁጠሪያ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index cc41f0f..755438e 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -570,8 +570,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"المتصفح"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"جهات الاتصال"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"البريد الإلكتروني"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"الرسائل القصيرة SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"الموسيقى"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"التقويم"</string>
diff --git a/packages/SystemUI/res/values-az-rAZ/strings.xml b/packages/SystemUI/res/values-az-rAZ/strings.xml
index 2e1438c..87b4433 100644
--- a/packages/SystemUI/res/values-az-rAZ/strings.xml
+++ b/packages/SystemUI/res/values-az-rAZ/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Brauzer"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontaktlar"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-poçt"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Musiqi"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Təqvim"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 28d0967..e173890 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Pregledač"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontakti"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Imejl"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Muzika"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalendar"</string>
diff --git a/packages/SystemUI/res/values-be-rBY/strings.xml b/packages/SystemUI/res/values-be-rBY/strings.xml
index 9f23d02..207e223 100644
--- a/packages/SystemUI/res/values-be-rBY/strings.xml
+++ b/packages/SystemUI/res/values-be-rBY/strings.xml
@@ -568,8 +568,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Браўзер"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Кантакты"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Электронная пошта"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS-паведамленні"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Музыка"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Каляндар"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 4ea50cf..16c94d7 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Браузър"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Контакти"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Електронна поща"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Музика"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Календар"</string>
diff --git a/packages/SystemUI/res/values-bn-rBD/strings.xml b/packages/SystemUI/res/values-bn-rBD/strings.xml
index 0d3e57a..feaa453 100644
--- a/packages/SystemUI/res/values-bn-rBD/strings.xml
+++ b/packages/SystemUI/res/values-bn-rBD/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"ব্রাউজার"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"পরিচিতি"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"ইমেল"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"সংগীত"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"ক্যালেন্ডার"</string>
diff --git a/packages/SystemUI/res/values-bs-rBA/strings.xml b/packages/SystemUI/res/values-bs-rBA/strings.xml
index 1c28298..f361b93 100644
--- a/packages/SystemUI/res/values-bs-rBA/strings.xml
+++ b/packages/SystemUI/res/values-bs-rBA/strings.xml
@@ -566,8 +566,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Preglednik"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontakti"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-pošta"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Muzika"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalendar"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index c4d82e4..8bedecd 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Navegador"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Contactes"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Correu electrònic"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Música"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Calendari"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 9366b00..0ff0f93 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -568,8 +568,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Prohlížeč"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontakty"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-mail"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Hudba"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalendář"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index a686306..20113da 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Browser"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontaktpersoner"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-mail"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"Sms"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Musik"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalender"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 8d55230..9721362 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Browser"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontakte"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-Mail"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Music"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalender"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 3dc97e7..276b856 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Πρόγραμμα περιήγησης"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Επαφές"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Ηλεκτρονικό ταχυδρομείο"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Μουσική"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Ημερολόγιο"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 8a3f385f..ca2e5b3 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Browser"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Contacts"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Email"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Music"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Calendar"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 8a3f385f..ca2e5b3 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Browser"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Contacts"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Email"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Music"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Calendar"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 8a3f385f..ca2e5b3 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Browser"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Contacts"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Email"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Music"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Calendar"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index e74d341..81696f6 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Navegador"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Contactos"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Correo electrónico"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Música"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Calendario"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 0994575..aa9578a 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Navegador"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Contactos"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Correo electrónico"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Música"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Calendario"</string>
diff --git a/packages/SystemUI/res/values-et-rEE/strings.xml b/packages/SystemUI/res/values-et-rEE/strings.xml
index 06145d9..ab81af5 100644
--- a/packages/SystemUI/res/values-et-rEE/strings.xml
+++ b/packages/SystemUI/res/values-et-rEE/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Brauser"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontaktid"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-post"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Muusika"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalender"</string>
diff --git a/packages/SystemUI/res/values-eu-rES/strings.xml b/packages/SystemUI/res/values-eu-rES/strings.xml
index 5c4f7c9..3617074 100644
--- a/packages/SystemUI/res/values-eu-rES/strings.xml
+++ b/packages/SystemUI/res/values-eu-rES/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Arakatzailea"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontaktuak"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Posta"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS mezuak"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Musika"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Calendar"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 341e942..48f0b954 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"مرورگر"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"مخاطبین"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"رایانامه"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"پیامک"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"موسیقی"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"تقویم"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 3065bee..5e1ca4b 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Selain"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Yhteystiedot"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Sähköposti"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"Tekstiviesti"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Musiikki"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalenteri"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 13eb6aa..765e0aa 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Navigateur"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Contacts"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Courriel"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"Message texte"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Musique"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Agenda"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 0b64c62..e66d342 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Navigateur"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Contacts"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Messagerie"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Musique"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Agenda"</string>
diff --git a/packages/SystemUI/res/values-gl-rES/strings.xml b/packages/SystemUI/res/values-gl-rES/strings.xml
index 54d2816..5ccf4a0 100644
--- a/packages/SystemUI/res/values-gl-rES/strings.xml
+++ b/packages/SystemUI/res/values-gl-rES/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Navegador"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Contactos"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Correo electrónico"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Música"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Calendario"</string>
diff --git a/packages/SystemUI/res/values-gu-rIN/strings.xml b/packages/SystemUI/res/values-gu-rIN/strings.xml
index e20bb40..84f9105 100644
--- a/packages/SystemUI/res/values-gu-rIN/strings.xml
+++ b/packages/SystemUI/res/values-gu-rIN/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"બ્રાઉઝર"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"સંપર્કો"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"ઇમેઇલ"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"સંગીત"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"કૅલેન્ડર"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index d4d0e85..89b5a6f 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"ब्राउज़र"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"संपर्क"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"ईमेल"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS करें"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"संगीत"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"कैलेंडर"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index ad77975..3c5812f 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Preglednik"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontakti"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-pošta"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Glazba"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalendar"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 5ddca19..f83ba28 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Böngésző"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Névjegyek"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-mail"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS-üzenetek"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Zene"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Naptár"</string>
diff --git a/packages/SystemUI/res/values-hy-rAM/strings.xml b/packages/SystemUI/res/values-hy-rAM/strings.xml
index 1c267d0..4ce22e4 100644
--- a/packages/SystemUI/res/values-hy-rAM/strings.xml
+++ b/packages/SystemUI/res/values-hy-rAM/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Դիտարկիչ"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Կոնտակտներ"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Էլփոստ"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Երաժշտություն"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Օրացույց"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index b9bbea0..4efffc56 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Browser"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontak"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Email"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Musik"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalender"</string>
diff --git a/packages/SystemUI/res/values-is-rIS/strings.xml b/packages/SystemUI/res/values-is-rIS/strings.xml
index b74341e..4875888 100644
--- a/packages/SystemUI/res/values-is-rIS/strings.xml
+++ b/packages/SystemUI/res/values-is-rIS/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Vafri"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Tengiliðir"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Tölvupóstur"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS-skilaboð"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Tónlist"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Dagatal"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 100df92..98a24248 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -344,7 +344,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Ricerca"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Su per <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"A sinistra per <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="3070506961866919502">"Non verrai disturbato da suoni e vibrazioni, ad eccezione di sveglie, promemoria, eventi e chiamate da contatti da te specificati."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Non ti disturberanno: suoni e vibrazioni, ad eccezione di sveglie, promemoria, eventi e chiamate da contatti da te specificati."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Personalizza"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Verranno bloccati TUTTI i suoni e le vibrazioni, anche di sveglie, musica, video e giochi. Potrai ancora telefonare."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Verranno bloccati TUTTI i suoni e le vibrazioni, anche di sveglie, musica, video e giochi."</string>
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Browser"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Contatti"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Email"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Musica"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Calendario"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 93d633a..ce73705 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -566,8 +566,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"דפדפן"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"אנשי קשר"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"אימייל"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"מוזיקה"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"יומן"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 92a35b1..c45fa4f 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -196,13 +196,13 @@
<string name="accessibility_quick_settings_airplane_on" msgid="6406141469157599296">"機内モードがONです。"</string>
<string name="accessibility_quick_settings_airplane_changed_off" msgid="66846307818850664">"機内モードをOFFにしました。"</string>
<string name="accessibility_quick_settings_airplane_changed_on" msgid="8983005603505087728">"機内モードをONにしました。"</string>
- <string name="accessibility_quick_settings_dnd_priority_on" msgid="1448402297221249355">"[通知を非表示]はONで、優先する通知のみです。"</string>
- <string name="accessibility_quick_settings_dnd_none_on" msgid="6882582132662613537">"[通知を非表示]はONで、サイレントです。"</string>
- <string name="accessibility_quick_settings_dnd_alarms_on" msgid="9152834845587554157">"[通知を非表示]はONで、アラームのみです。"</string>
- <string name="accessibility_quick_settings_dnd" msgid="6607873236717185815">"通知を非表示"</string>
- <string name="accessibility_quick_settings_dnd_off" msgid="2371832603753738581">"[通知を非表示]はOFFです。"</string>
- <string name="accessibility_quick_settings_dnd_changed_off" msgid="898107593453022935">"[通知を非表示]をOFFにしました。"</string>
- <string name="accessibility_quick_settings_dnd_changed_on" msgid="4483780856613561039">"[通知を非表示]をONにしました。"</string>
+ <string name="accessibility_quick_settings_dnd_priority_on" msgid="1448402297221249355">"マナーモードは ON で、優先する通知のみ許可します。"</string>
+ <string name="accessibility_quick_settings_dnd_none_on" msgid="6882582132662613537">"マナーモードは ON で、サイレント モードです。"</string>
+ <string name="accessibility_quick_settings_dnd_alarms_on" msgid="9152834845587554157">"マナーモードは ON で、アラームのみ許可します。"</string>
+ <string name="accessibility_quick_settings_dnd" msgid="6607873236717185815">"マナーモード"</string>
+ <string name="accessibility_quick_settings_dnd_off" msgid="2371832603753738581">"マナーモードは OFF です。"</string>
+ <string name="accessibility_quick_settings_dnd_changed_off" msgid="898107593453022935">"マナーモードを OFF にしました。"</string>
+ <string name="accessibility_quick_settings_dnd_changed_on" msgid="4483780856613561039">"マナーモードを ON にしました。"</string>
<string name="accessibility_quick_settings_bluetooth" msgid="6341675755803320038">"Bluetooth"</string>
<string name="accessibility_quick_settings_bluetooth_off" msgid="2133631372372064339">"BluetoothがOFFです。"</string>
<string name="accessibility_quick_settings_bluetooth_on" msgid="7681999166216621838">"BluetoothがONです。"</string>
@@ -263,7 +263,7 @@
<string name="dessert_case" msgid="1295161776223959221">"デザートケース"</string>
<string name="start_dreams" msgid="5640361424498338327">"スクリーン セーバー"</string>
<string name="ethernet_label" msgid="7967563676324087464">"イーサネット"</string>
- <string name="quick_settings_dnd_label" msgid="8735855737575028208">"通知を非表示"</string>
+ <string name="quick_settings_dnd_label" msgid="8735855737575028208">"マナーモード"</string>
<string name="quick_settings_dnd_priority_label" msgid="483232950670692036">"優先する通知のみ"</string>
<string name="quick_settings_dnd_alarms_label" msgid="2559229444312445858">"アラームのみ"</string>
<string name="quick_settings_dnd_none_label" msgid="5025477807123029478">"サイレント"</string>
@@ -564,15 +564,14 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"ブラウザ"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"連絡先"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"メール"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"音楽"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"カレンダー"</string>
<string name="tuner_full_zen_title" msgid="4540823317772234308">"音量調節を表示"</string>
- <string name="volume_and_do_not_disturb" msgid="3373784330208603030">"通知の非表示"</string>
+ <string name="volume_and_do_not_disturb" msgid="3373784330208603030">"マナーモード"</string>
<string name="volume_dnd_silent" msgid="4363882330723050727">"音量ボタンのショートカット"</string>
- <string name="volume_up_silent" msgid="7141255269783588286">"音量上げボタンで [通知を非表示] を OFF にする"</string>
+ <string name="volume_up_silent" msgid="7141255269783588286">"音量上げボタンでマナーモードを OFF にする"</string>
<string name="battery" msgid="7498329822413202973">"電池"</string>
<string name="clock" msgid="7416090374234785905">"時計"</string>
<string name="headset" msgid="4534219457597457353">"ヘッドセット"</string>
diff --git a/packages/SystemUI/res/values-ka-rGE/strings.xml b/packages/SystemUI/res/values-ka-rGE/strings.xml
index efc3940..a151b8f 100644
--- a/packages/SystemUI/res/values-ka-rGE/strings.xml
+++ b/packages/SystemUI/res/values-ka-rGE/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"ბრაუზერი"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"კონტაქტები"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"ელფოსტა"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"მუსიკა"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"კალენდარი"</string>
diff --git a/packages/SystemUI/res/values-kk-rKZ/strings.xml b/packages/SystemUI/res/values-kk-rKZ/strings.xml
index 271035f..6c0c967 100644
--- a/packages/SystemUI/res/values-kk-rKZ/strings.xml
+++ b/packages/SystemUI/res/values-kk-rKZ/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Браузер"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Контактілер"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Электрондық пошта"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"Мәтіндік хабар"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Mузыка"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Күнтізбе"</string>
diff --git a/packages/SystemUI/res/values-km-rKH/strings.xml b/packages/SystemUI/res/values-km-rKH/strings.xml
index 874993f..d3e97cc 100644
--- a/packages/SystemUI/res/values-km-rKH/strings.xml
+++ b/packages/SystemUI/res/values-km-rKH/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"កម្មវិធីរុករក"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"ទំនាក់ទំនង"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"អ៊ីមែល"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"សារ SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"តន្ត្រី"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"ប្រតិទិន"</string>
diff --git a/packages/SystemUI/res/values-kn-rIN/strings.xml b/packages/SystemUI/res/values-kn-rIN/strings.xml
index 6f1f28c..c71d67e 100644
--- a/packages/SystemUI/res/values-kn-rIN/strings.xml
+++ b/packages/SystemUI/res/values-kn-rIN/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"ಬ್ರೌಸರ್"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"ಸಂಪರ್ಕಗಳು"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"ಇಮೇಲ್"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"ಎಸ್ಎಂಎಸ್"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"ಸಂಗೀತ"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"ಕ್ಯಾಲೆಂಡರ್"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 0a98831..3827dba 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -464,7 +464,7 @@
<string name="show_battery_percentage_summary" msgid="3215025775576786037">"충전 중이 아닌 경우 상태 표시줄 아이콘 내에 배터리 잔량 비율 표시"</string>
<string name="quick_settings" msgid="10042998191725428">"빠른 설정"</string>
<string name="status_bar" msgid="4877645476959324760">"상태 표시줄"</string>
- <string name="overview" msgid="4018602013895926956">"개요"</string>
+ <string name="overview" msgid="4018602013895926956">"최근 사용"</string>
<string name="demo_mode" msgid="2389163018533514619">"데모 모드"</string>
<string name="enable_demo_mode" msgid="4844205668718636518">"데모 모드 사용"</string>
<string name="show_demo_mode" msgid="2018336697782464029">"데모 모드 표시"</string>
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"브라우저"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"주소록"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"이메일"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"음악"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"캘린더"</string>
diff --git a/packages/SystemUI/res/values-ky-rKG/strings.xml b/packages/SystemUI/res/values-ky-rKG/strings.xml
index 47abacd..56c8360 100644
--- a/packages/SystemUI/res/values-ky-rKG/strings.xml
+++ b/packages/SystemUI/res/values-ky-rKG/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Серепчи"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Байланыштар"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Электрондук почта"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Музыка"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Жылнаама"</string>
diff --git a/packages/SystemUI/res/values-lo-rLA/strings.xml b/packages/SystemUI/res/values-lo-rLA/strings.xml
index 7f2e2a0..47a49da 100644
--- a/packages/SystemUI/res/values-lo-rLA/strings.xml
+++ b/packages/SystemUI/res/values-lo-rLA/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"ໂປຣແກຣມທ່ອງເວັບ"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"ລາຍຊື່ຜູ້ຕິດຕໍ່"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"ອີເມວ"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"ຂໍ້ຄວາມສັ້ນ(SMS)"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"ດົນຕີ"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"ປະຕິທິນ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 6514008..c4676fa 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -566,8 +566,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Naršyklė"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontaktai"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"El. paštas"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Muzika"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalendorius"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index fb736a0..a8266dd 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Pārlūkprogramma"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontaktpersonas"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-pasts"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"Īsziņas"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Mūzika"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalendārs"</string>
diff --git a/packages/SystemUI/res/values-mk-rMK/strings.xml b/packages/SystemUI/res/values-mk-rMK/strings.xml
index 7b65a92..c5e2500e 100644
--- a/packages/SystemUI/res/values-mk-rMK/strings.xml
+++ b/packages/SystemUI/res/values-mk-rMK/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Прелистувач"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Контакти"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Е-пошта"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Музика"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Календар"</string>
diff --git a/packages/SystemUI/res/values-ml-rIN/strings.xml b/packages/SystemUI/res/values-ml-rIN/strings.xml
index b4d085e..a1a3b31 100644
--- a/packages/SystemUI/res/values-ml-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ml-rIN/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"ബ്രൗസർ"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"കോൺടാക്റ്റുകൾ"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"ഇമെയിൽ"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS:"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"സംഗീതം"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"കലണ്ടർ"</string>
diff --git a/packages/SystemUI/res/values-mn-rMN/strings.xml b/packages/SystemUI/res/values-mn-rMN/strings.xml
index 8f762fb..9b83058 100644
--- a/packages/SystemUI/res/values-mn-rMN/strings.xml
+++ b/packages/SystemUI/res/values-mn-rMN/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Хөтөч"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Харилцагчид"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Имэйл"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Хөгжим"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Хуанли"</string>
diff --git a/packages/SystemUI/res/values-mr-rIN/strings.xml b/packages/SystemUI/res/values-mr-rIN/strings.xml
index b4d8b1a..8003890 100644
--- a/packages/SystemUI/res/values-mr-rIN/strings.xml
+++ b/packages/SystemUI/res/values-mr-rIN/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"ब्राउझर"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"संपर्क"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"ईमेल"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"संगीत"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"कॅलेंडर"</string>
diff --git a/packages/SystemUI/res/values-ms-rMY/strings.xml b/packages/SystemUI/res/values-ms-rMY/strings.xml
index ca54993..1a16967 100644
--- a/packages/SystemUI/res/values-ms-rMY/strings.xml
+++ b/packages/SystemUI/res/values-ms-rMY/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Penyemak imbas"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kenalan"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-mel"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Muzik"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalendar"</string>
diff --git a/packages/SystemUI/res/values-my-rMM/strings.xml b/packages/SystemUI/res/values-my-rMM/strings.xml
index 7032677..17b6802 100644
--- a/packages/SystemUI/res/values-my-rMM/strings.xml
+++ b/packages/SystemUI/res/values-my-rMM/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"ဘရောင်ဇာ"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"အဆက်အသွယ်များ"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"အီးမေးလ်"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS စာတိုစနစ်"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Music"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Calendar"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 068c2ca..cb691c5 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Nettleser"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontakter"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-post"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Musikk"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalender"</string>
diff --git a/packages/SystemUI/res/values-ne-rNP/strings.xml b/packages/SystemUI/res/values-ne-rNP/strings.xml
index d88f37f..97f5f5e 100644
--- a/packages/SystemUI/res/values-ne-rNP/strings.xml
+++ b/packages/SystemUI/res/values-ne-rNP/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"ब्राउजर"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"सम्पर्कहरू"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"इमेल"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"संगीत"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"पात्रो"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index d07de09..43c577d 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Browser"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Contacten"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-mail"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"Sms"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Muziek"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Agenda"</string>
diff --git a/packages/SystemUI/res/values-pa-rIN/strings.xml b/packages/SystemUI/res/values-pa-rIN/strings.xml
index 32397f6..6ef8674 100644
--- a/packages/SystemUI/res/values-pa-rIN/strings.xml
+++ b/packages/SystemUI/res/values-pa-rIN/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"ਬ੍ਰਾਊਜ਼ਰ"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"ਸੰਪਰਕ"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"ਈਮੇਲ"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"ਸੰਗੀਤ"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"ਕੈਲੰਡਰ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 511872a..03e599c 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -566,8 +566,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Przeglądarka"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontakty"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-mail"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Muzyka"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalendarz"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 07b0d50..0ae421d 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Navegador"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Contatos"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-mail"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Música"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Agenda"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index b9c96b6..6ad19f9 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Navegador"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Contactos"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Email"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Música"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Calendário"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 07b0d50..0ae421d 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Navegador"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Contatos"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-mail"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Música"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Agenda"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 316c7cf..70ef6ea 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -566,8 +566,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Browser"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Agendă"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-mail"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Muzică"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Calendar"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 7264b95..41ec367 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -568,8 +568,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Браузер"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Контакты"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Эл. почта"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Музыка"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Календарь"</string>
diff --git a/packages/SystemUI/res/values-si-rLK/strings.xml b/packages/SystemUI/res/values-si-rLK/strings.xml
index 0dc2f9f..8f65531 100644
--- a/packages/SystemUI/res/values-si-rLK/strings.xml
+++ b/packages/SystemUI/res/values-si-rLK/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"බ්රවුසරය"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"සම්බන්ධතා"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"ඊ-තැපෑල"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"සංගීතය"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"දින දර්ශනය"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 4cdb97e..276c266 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -568,8 +568,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Prehliadač"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontakty"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-mail"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Hudba"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalendár"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 6df7acc..cda2ac9 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -568,8 +568,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Brskalnik"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Stiki"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-pošta"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"Sporočila SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Glasba"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Koledar"</string>
diff --git a/packages/SystemUI/res/values-sq-rAL/strings.xml b/packages/SystemUI/res/values-sq-rAL/strings.xml
index ba4cea0..33b2d5b 100644
--- a/packages/SystemUI/res/values-sq-rAL/strings.xml
+++ b/packages/SystemUI/res/values-sq-rAL/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Shfletuesi"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontaktet"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Mail-i"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Muzikë"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalendari"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 4fdbb98..11d05b7 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Прегледач"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Контакти"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Имејл"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Музика"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Календар"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 7ccf197..46e3f05 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Webbläsare"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontakter"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-post"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"Sms"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Musik"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalender"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 0951594..43aa36d 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Kivinjari"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Anwani"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Barua pepe"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Muziki"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalenda"</string>
diff --git a/packages/SystemUI/res/values-ta-rIN/strings.xml b/packages/SystemUI/res/values-ta-rIN/strings.xml
index 7df7ac5..364173e 100644
--- a/packages/SystemUI/res/values-ta-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ta-rIN/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"உலாவி"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"தொடர்புகள்"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"மின்னஞ்சல்"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"மியூசிக்"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"கேலெண்டர்"</string>
diff --git a/packages/SystemUI/res/values-te-rIN/strings.xml b/packages/SystemUI/res/values-te-rIN/strings.xml
index eff3a03..c1e55c8 100644
--- a/packages/SystemUI/res/values-te-rIN/strings.xml
+++ b/packages/SystemUI/res/values-te-rIN/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"బ్రౌజర్"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"పరిచయాలు"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"ఇమెయిల్"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"సంగీతం"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"క్యాలెండర్"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 440d02f..f2b1772 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"เบราว์เซอร์"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"รายชื่อติดต่อ"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"อีเมล"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"เพลง"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"ปฏิทิน"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 30dc7f5..f58ed17 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Browser"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Mga Contact"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Email"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Music"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Kalendaryo"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 740fdc6..e7b1abb 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Tarayıcı"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kişiler"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-posta"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Müzik"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Takvim"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index eae7400..886a138 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -568,8 +568,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Веб-переглядач"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Контакти"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Електронна пошта"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Музика"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Календар"</string>
diff --git a/packages/SystemUI/res/values-ur-rPK/strings.xml b/packages/SystemUI/res/values-ur-rPK/strings.xml
index b8781f5..31d90e2 100644
--- a/packages/SystemUI/res/values-ur-rPK/strings.xml
+++ b/packages/SystemUI/res/values-ur-rPK/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"براؤزر"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"رابطے"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"ای میل"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"موسیقی"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"کیلنڈر"</string>
diff --git a/packages/SystemUI/res/values-uz-rUZ/strings.xml b/packages/SystemUI/res/values-uz-rUZ/strings.xml
index e022165..78ad148 100644
--- a/packages/SystemUI/res/values-uz-rUZ/strings.xml
+++ b/packages/SystemUI/res/values-uz-rUZ/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Brauzer"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Kontaktlar"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"E-pochta"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Musiqa"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Taqvim"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 04e1f89..ba49055 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Trình duyệt"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Danh bạ"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"Email"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Âm nhạc"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Lịch"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 1982b57..7a7991f 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"浏览器"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"通讯录"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"电子邮件"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"短信"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"音乐"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"日历"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 31a92ee..4f7ffe5 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -564,8 +564,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"瀏覽器"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"通訊錄"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"電郵"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"短訊"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"音樂"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"日曆"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 6aec964..2de07e3 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"瀏覽器"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"聯絡人"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"電子郵件"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"簡訊"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"音樂"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"日曆"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index d85fcbf..6be736a 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -562,8 +562,7 @@
<string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"Isiphequluli"</string>
<string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"Oxhumana nabo"</string>
<string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"I-imeyili"</string>
- <!-- no translation found for keyboard_shortcut_group_applications_sms (638701213803242744) -->
- <skip />
+ <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"I-SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"Umculo"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"I-YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"Ikhalenda"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 549d50e..12f7881 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -694,4 +694,7 @@
<!-- The alpha to apply to the recents row when it doesn't have focus -->
<item name="recents_recents_row_dim_alpha" format="float" type="dimen">0.5</item>
+
+ <!-- The size of the PIP dismiss target. -->
+ <dimen name="pip_dismiss_target_size">48dp</dimen>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index bfc8642..99e7876 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -42,7 +42,7 @@
import com.android.systemui.statusbar.SystemBars;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tv.pip.PipUI;
+import com.android.systemui.pip.PipUI;
import com.android.systemui.usb.StorageNotification;
import com.android.systemui.volume.VolumeUI;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
new file mode 100644
index 0000000..617d8ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 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.pip;
+
+import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+
+import com.android.systemui.SystemUI;
+
+/**
+ * Controls the picture-in-picture window.
+ */
+public class PipUI extends SystemUI {
+
+ private boolean mSupportsPip;
+ private boolean mIsLeanBackOnly;
+
+ @Override
+ public void start() {
+ PackageManager pm = mContext.getPackageManager();
+ mSupportsPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
+ mIsLeanBackOnly = pm.hasSystemFeature(FEATURE_LEANBACK_ONLY);
+ if (!mSupportsPip) {
+ return;
+ }
+ if (mIsLeanBackOnly) {
+ com.android.systemui.pip.tv.PipManager.getInstance().initialize(mContext);
+ } else {
+ com.android.systemui.pip.phone.PipManager.getInstance().initialize(mContext);
+ }
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (!mSupportsPip) {
+ return;
+ }
+ if (mIsLeanBackOnly) {
+ com.android.systemui.pip.tv.PipManager.getInstance().onConfigurationChanged();
+ } else {
+ com.android.systemui.pip.phone.PipManager.getInstance().onConfigurationChanged();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
new file mode 100644
index 0000000..a7ac719
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 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.pip.phone;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnLayoutChangeListener;
+import android.view.WindowManager;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+
+public class PipDismissViewController {
+
+ // This delay controls how long to wait before we show the target when the user first moves
+ // the PIP, to prevent the target from animating if the user just wants to fling the PIP
+ private static final int SHOW_TARGET_DELAY = 100;
+ private static final int SHOW_TARGET_DURATION = 200;
+
+ private Context mContext;
+ private WindowManager mWindowManager;
+
+ private View mDismissView;
+ private Rect mDismissTargetScreenBounds = new Rect();
+
+ public PipDismissViewController(Context context) {
+ mContext = context;
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ /**
+ * Creates the dismiss target for showing via {@link #showDismissTarget()}.
+ */
+ public void createDismissTarget() {
+ if (mDismissView == null) {
+ // Create a new view for the dismiss target
+ int dismissTargetSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.pip_dismiss_target_size);
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ mDismissView = inflater.inflate(R.layout.pip_dismiss_view, null);
+ mDismissView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ if (mDismissView != null) {
+ mDismissView.getBoundsOnScreen(mDismissTargetScreenBounds);
+ }
+ }
+ });
+
+ // Add the target to the window
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ dismissTargetSize,
+ dismissTargetSize,
+ WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ lp.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
+ mWindowManager.addView(mDismissView, lp);
+ }
+ mDismissView.animate().cancel();
+ }
+
+ /**
+ * Shows the dismiss target.
+ */
+ public void showDismissTarget() {
+ mDismissView.animate()
+ .alpha(1f)
+ .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+ .setStartDelay(SHOW_TARGET_DELAY)
+ .setDuration(SHOW_TARGET_DURATION)
+ .start();
+ }
+
+ /**
+ * Hides and destroys the dismiss target.
+ */
+ public void destroyDismissTarget() {
+ if (mDismissView != null) {
+ mDismissView.animate()
+ .alpha(0f)
+ .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
+ .setStartDelay(0)
+ .setDuration(SHOW_TARGET_DURATION)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mWindowManager.removeView(mDismissView);
+ mDismissView = null;
+ }
+ })
+ .start();
+ }
+ }
+
+ /**
+ * @return the dismiss target screen bounds.
+ */
+ public Rect getDismissBounds() {
+ return mDismissTargetScreenBounds;
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
new file mode 100644
index 0000000..53a4868
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 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.pip.phone;
+
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+/**
+ * Manages the picture-in-picture (PIP) UI and states for Phones.
+ */
+public class PipManager {
+ private static final String TAG = "PipManager";
+
+ private static PipManager sPipController;
+
+ private Context mContext;
+ private IActivityManager mActivityManager;
+ private IWindowManager mWindowManager;
+
+ private PipTouchHandler mTouchHandler;
+
+ private PipManager() {}
+
+ /**
+ * Initializes {@link PipManager}.
+ */
+ public void initialize(Context context) {
+ mContext = context;
+ mActivityManager = ActivityManagerNative.getDefault();
+ mWindowManager = WindowManagerGlobal.getWindowManagerService();
+
+ mTouchHandler = new PipTouchHandler(context, mActivityManager, mWindowManager);
+ }
+
+ /**
+ * Updates the PIP per configuration changed.
+ */
+ public void onConfigurationChanged() {}
+
+ /**
+ * Gets an instance of {@link PipManager}.
+ */
+ public static PipManager getInstance() {
+ if (sPipController == null) {
+ sPipController = new PipManager();
+ }
+ return sPipController;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
new file mode 100644
index 0000000..7aa9aa5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2016 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.pip.phone;
+
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.view.WindowManager.INPUT_CONSUMER_PIP;
+
+import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
+import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.systemui.recents.misc.Utilities.RECT_EVALUATOR;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager.StackInfo;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IWindowManager;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.animation.Interpolator;
+import android.widget.Scroller;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+
+/**
+ * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
+ * the PIP.
+ */
+public class PipTouchHandler {
+ private static final String TAG = "PipTouchHandler";
+ private static final boolean DEBUG_ALLOW_OUT_OF_BOUNDS_STACK = false;
+
+ private static final int SNAP_STACK_DURATION = 225;
+ private static final int DISMISS_STACK_DURATION = 375;
+ private static final int EXPAND_STACK_DURATION = 225;
+
+ private static final float SCROLL_FRICTION_MULTIPLIER = 8f;
+
+ private final Context mContext;
+ private final IActivityManager mActivityManager;
+ private final ViewConfiguration mViewConfig;
+ private final InputChannel mInputChannel = new InputChannel();
+
+ private final PipInputEventReceiver mInputEventReceiver;
+ private final PipDismissViewController mDismissViewController;
+
+ private final Rect mPinnedStackBounds = new Rect();
+ private final Rect mBoundedPinnedStackBounds = new Rect();
+ private ValueAnimator mPinnedStackBoundsAnimator = null;
+
+ private final PointF mDownTouch = new PointF();
+ private final PointF mLastTouch = new PointF();
+ private boolean mIsDragging;
+ private int mActivePointerId;
+
+ private final FlingAnimationUtils mFlingAnimationUtils;
+ private final Scroller mScroller;
+ private VelocityTracker mVelocityTracker;
+
+ /**
+ * Input handler used for Pip windows.
+ */
+ private final class PipInputEventReceiver extends InputEventReceiver {
+ public PipInputEventReceiver(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = true;
+ try {
+ // To be implemented for input handling over Pip windows
+ if (event instanceof MotionEvent) {
+ MotionEvent ev = (MotionEvent) event;
+ handleTouchEvent(ev);
+ }
+ } finally {
+ finishInputEvent(event, handled);
+ }
+ }
+ }
+
+ public PipTouchHandler(Context context, IActivityManager activityManager,
+ IWindowManager windowManager) {
+
+ // Initialize the Pip input consumer
+ try {
+ windowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
+ windowManager.createInputConsumer(INPUT_CONSUMER_PIP, mInputChannel);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to create PIP input consumer", e);
+ }
+ mContext = context;
+ mActivityManager = activityManager;
+ mViewConfig = ViewConfiguration.get(context);
+ mInputEventReceiver = new PipInputEventReceiver(mInputChannel, Looper.myLooper());
+ mDismissViewController = new PipDismissViewController(context);
+ mScroller = new Scroller(context);
+ mScroller.setFriction(mViewConfig.getScrollFriction() * SCROLL_FRICTION_MULTIPLIER);
+ mFlingAnimationUtils = new FlingAnimationUtils(context, 2f);
+ }
+
+ private void handleTouchEvent(MotionEvent ev) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN: {
+ // Cancel any existing animations on the pinned stack
+ if (mPinnedStackBoundsAnimator != null) {
+ mPinnedStackBoundsAnimator.cancel();
+ }
+
+ updateBoundedPinnedStackBounds();
+ initOrResetVelocityTracker();
+ mVelocityTracker.addMovement(ev);
+ mActivePointerId = ev.getPointerId(0);
+ mLastTouch.set(ev.getX(), ev.getY());
+ mDownTouch.set(mLastTouch);
+ mIsDragging = false;
+ // TODO: Consider setting a timer such at after X time, we show the dismiss target
+ // if the user hasn't already dragged some distance
+ mDismissViewController.createDismissTarget();
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ // Update the velocity tracker
+ mVelocityTracker.addMovement(ev);
+
+ int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (!mIsDragging) {
+ // Check if the pointer has moved far enough
+ float movement = PointF.length(mDownTouch.x - ev.getX(activePointerIndex),
+ mDownTouch.y - ev.getY(activePointerIndex));
+ if (movement > mViewConfig.getScaledTouchSlop()) {
+ mIsDragging = true;
+ mDismissViewController.showDismissTarget();
+ }
+ }
+
+ if (mIsDragging) {
+ // Move the pinned stack
+ float dx = ev.getX(activePointerIndex) - mLastTouch.x;
+ float dy = ev.getY(activePointerIndex) - mLastTouch.y;
+ float left = Math.max(mBoundedPinnedStackBounds.left, Math.min(
+ mBoundedPinnedStackBounds.right, mPinnedStackBounds.left + dx));
+ float top = Math.max(mBoundedPinnedStackBounds.top, Math.min(
+ mBoundedPinnedStackBounds.bottom, mPinnedStackBounds.top + dy));
+ if (DEBUG_ALLOW_OUT_OF_BOUNDS_STACK) {
+ left = mPinnedStackBounds.left + dx;
+ top = mPinnedStackBounds.top + dy;
+ }
+ movePinnedStack(left, top);
+ }
+ mLastTouch.set(ev.getX(), ev.getY());
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_UP: {
+ // Update the velocity tracker
+ mVelocityTracker.addMovement(ev);
+
+ int pointerIndex = ev.getActionIndex();
+ int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // Select a new active pointer id and reset the movement state
+ final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ mLastTouch.set(ev.getX(newPointerIndex), ev.getY(newPointerIndex));
+ }
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ // Update the velocity tracker
+ mVelocityTracker.addMovement(ev);
+
+ if (mIsDragging) {
+ mVelocityTracker.computeCurrentVelocity(1000,
+ ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
+ float velocityX = mVelocityTracker.getXVelocity();
+ float velocityY = mVelocityTracker.getYVelocity();
+ float velocity = PointF.length(velocityX, velocityY);
+ if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+ flingToSnapTarget(velocity, velocityX, velocityY);
+ } else {
+ int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ int x = (int) ev.getX(activePointerIndex);
+ int y = (int) ev.getY(activePointerIndex);
+ Rect dismissBounds = mDismissViewController.getDismissBounds();
+ if (dismissBounds.contains(x, y)) {
+ animateDismissPinnedStack(dismissBounds);
+ } else {
+ animateToSnapTarget();
+ }
+ }
+ } else {
+ expandPinnedStackToFullscreen();
+ }
+ mDismissViewController.destroyDismissTarget();
+
+ // Fall through to clean up
+ }
+ case MotionEvent.ACTION_CANCEL: {
+ mIsDragging = false;
+ recycleVelocityTracker();
+ break;
+ }
+ }
+ }
+
+ private void initOrResetVelocityTracker() {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ } else {
+ mVelocityTracker.clear();
+ }
+ }
+
+ private void recycleVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ /**
+ * Creates an animation that continues the fling to a snap target.
+ */
+ private void flingToSnapTarget(float velocity, float velocityX, float velocityY) {
+ mScroller.fling(mPinnedStackBounds.left, mPinnedStackBounds.top,
+ (int) velocityX, (int) velocityY,
+ mBoundedPinnedStackBounds.left, mBoundedPinnedStackBounds.right,
+ mBoundedPinnedStackBounds.top, mBoundedPinnedStackBounds.bottom);
+ Rect toBounds = findClosestBoundedPinnedStackSnapTarget(
+ mScroller.getFinalX(), mScroller.getFinalY());
+ mScroller.abortAnimation();
+ if (!mPinnedStackBounds.equals(toBounds)) {
+ mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
+ toBounds, 0, FAST_OUT_SLOW_IN);
+ mFlingAnimationUtils.apply(mPinnedStackBoundsAnimator, 0,
+ distanceBetweenRectOffsets(mPinnedStackBounds, toBounds),
+ velocity);
+ mPinnedStackBoundsAnimator.start();
+ }
+ }
+
+ /**
+ * Animates the pinned stack to the closest snap target.
+ */
+ private void animateToSnapTarget() {
+ Rect toBounds = findClosestBoundedPinnedStackSnapTarget(
+ mPinnedStackBounds.left, mPinnedStackBounds.top);
+ if (!mPinnedStackBounds.equals(toBounds)) {
+ mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
+ toBounds, SNAP_STACK_DURATION, FAST_OUT_SLOW_IN);
+ mPinnedStackBoundsAnimator.start();
+ }
+ }
+
+ /**
+ * Animates the dismissal of the pinned stack into the given bounds.
+ */
+ private void animateDismissPinnedStack(Rect dismissBounds) {
+ Rect toBounds = new Rect(dismissBounds.centerX(),
+ dismissBounds.centerY(),
+ dismissBounds.centerX() + 1,
+ dismissBounds.centerY() + 1);
+ mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
+ toBounds, DISMISS_STACK_DURATION, FAST_OUT_LINEAR_IN);
+ mPinnedStackBoundsAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ BackgroundThread.getHandler().post(() -> {
+ try {
+ mActivityManager.removeStack(PINNED_STACK_ID);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to remove PIP", e);
+ }
+ });
+ }
+ });
+ mPinnedStackBoundsAnimator.start();
+ }
+
+ /**
+ * Resizes the pinned stack back to fullscreen.
+ */
+ private void expandPinnedStackToFullscreen() {
+ BackgroundThread.getHandler().post(() -> {
+ try {
+ mActivityManager.resizeStack(PINNED_STACK_ID, null /* bounds */,
+ true /* allowResizeInDockedMode */, true /* preserveWindows */,
+ true /* animate */, EXPAND_STACK_DURATION);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error showing PIP menu activity", e);
+ }
+ });
+ }
+
+ /**
+ * Updates the movement bounds of the pinned stack.
+ */
+ private void updateBoundedPinnedStackBounds() {
+ try {
+ StackInfo info = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ mPinnedStackBounds.set(info.bounds);
+ mBoundedPinnedStackBounds.set(mActivityManager.getPictureInPictureMovementBounds());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not fetch PIP movement bounds.", e);
+ }
+ }
+
+ /**
+ * Moves the pinned stack to the given {@param left} and {@param top} offsets.
+ */
+ private void movePinnedStack(float left, float top) {
+ if ((int) left != mPinnedStackBounds.left || (int) top != mPinnedStackBounds.top) {
+ mPinnedStackBounds.offsetTo((int) left, (int) top);
+ BackgroundThread.getHandler().post(() -> {
+ try {
+ mActivityManager.resizePinnedStack(mPinnedStackBounds,
+ null /* tempPinnedBounds */);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not move pinned stack to offset: (" + left + ", " + top + ")",
+ e);
+ }
+ });
+ }
+ }
+
+ /**
+ * Resizes the pinned stack to the given {@param bounds}.
+ */
+ private void resizePinnedStack(Rect bounds) {
+ if (!mPinnedStackBounds.equals(bounds)) {
+ mPinnedStackBounds.set(bounds);
+ BackgroundThread.getHandler().post(() -> {
+ try {
+ mActivityManager.resizePinnedStack(bounds, null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not resize pinned stack to bounds: (" + bounds + ")");
+ }
+ });
+ }
+ }
+
+ /**
+ * Creates a resize-stack animation.
+ */
+ private ValueAnimator createResizePinnedStackAnimation(Rect toBounds, int duration,
+ Interpolator interpolator) {
+ ValueAnimator anim = ValueAnimator.ofObject(RECT_EVALUATOR,
+ mPinnedStackBounds, toBounds);
+ anim.setDuration(duration);
+ anim.setInterpolator(interpolator);
+ anim.addUpdateListener(
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ resizePinnedStack((Rect) animation.getAnimatedValue());
+ }
+ });
+ return anim;
+ }
+
+ /**
+ * @return the closest absolute bounded stack left/top to the given {@param x} and {@param y}.
+ */
+ private Rect findClosestBoundedPinnedStackSnapTarget(int x, int y) {
+ Point[] snapTargets;
+ int orientation = mContext.getResources().getConfiguration().orientation;
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ snapTargets = new Point[] {
+ new Point(mBoundedPinnedStackBounds.left, mBoundedPinnedStackBounds.top),
+ new Point(mBoundedPinnedStackBounds.right, mBoundedPinnedStackBounds.top),
+ new Point(mBoundedPinnedStackBounds.left, mBoundedPinnedStackBounds.bottom),
+ new Point(mBoundedPinnedStackBounds.right, mBoundedPinnedStackBounds.bottom)
+ };
+ } else {
+ snapTargets = new Point[] {
+ new Point(mBoundedPinnedStackBounds.left, mBoundedPinnedStackBounds.top),
+ new Point(mBoundedPinnedStackBounds.right, mBoundedPinnedStackBounds.top),
+ new Point(mBoundedPinnedStackBounds.left, mBoundedPinnedStackBounds.bottom),
+ new Point(mBoundedPinnedStackBounds.right, mBoundedPinnedStackBounds.bottom)
+ };
+ }
+
+ Point closestSnapTarget = null;
+ float minDistance = Float.MAX_VALUE;
+ for (Point p : snapTargets) {
+ float distance = distanceToPoint(p, x, y);
+ if (distance < minDistance) {
+ closestSnapTarget = p;
+ minDistance = distance;
+ }
+ }
+
+ Rect toBounds = new Rect(mPinnedStackBounds);
+ toBounds.offsetTo(closestSnapTarget.x, closestSnapTarget.y);
+ return toBounds;
+ }
+
+ /**
+ * @return the distance between point {@param p} and the given {@param x} and {@param y}.
+ */
+ private float distanceToPoint(Point p, int x, int y) {
+ return PointF.length(p.x - x, p.y - y);
+ }
+
+
+ /**
+ * @return the distance between points {@param p1} and {@param p2}.
+ */
+ private float distanceBetweenRectOffsets(Rect r1, Rect r2) {
+ return PointF.length(r1.left - r2.left, r1.top - r2.top);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlButtonView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/tv/pip/PipControlButtonView.java
rename to packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java
index 80c593c..59cb086 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.tv.pip;
+package com.android.systemui.pip.tv;
import android.animation.Animator;
import android.animation.AnimatorInflater;
@@ -22,7 +22,6 @@
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
-import android.view.View.OnFocusChangeListener;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java
rename to packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
index 71740ce..a2aff2d 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.tv.pip;
+package com.android.systemui.pip.tv;
import android.content.Context;
import android.media.session.MediaController;
@@ -22,9 +22,6 @@
import android.view.View;
import android.view.Gravity;
import android.view.LayoutInflater;
-import android.view.View.OnFocusChangeListener;
-import android.widget.ImageView;
-import android.widget.TextView;
import android.widget.LinearLayout;
import android.util.AttributeSet;
@@ -33,10 +30,6 @@
import static android.media.session.PlaybackState.ACTION_PAUSE;
import static android.media.session.PlaybackState.ACTION_PLAY;
-import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_PLAYING;
-import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_PAUSED;
-import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_UNAVAILABLE;
-
/**
* A view containing PIP controls including fullscreen, close, and media controls.
@@ -145,9 +138,9 @@
}
long actions = mMediaController.getPlaybackState().getActions();
int state = mMediaController.getPlaybackState().getState();
- if (mPipManager.getPlaybackState() == PLAYBACK_STATE_PAUSED) {
+ if (mPipManager.getPlaybackState() == PipManager.PLAYBACK_STATE_PAUSED) {
mMediaController.getTransportControls().play();
- } else if (mPipManager.getPlaybackState() == PLAYBACK_STATE_PLAYING) {
+ } else if (mPipManager.getPlaybackState() == PipManager.PLAYBACK_STATE_PLAYING) {
mMediaController.getTransportControls().pause();
}
// View will be updated later in {@link mMediaControllerCallback}
@@ -188,11 +181,11 @@
private void updatePlayPauseView() {
int state = mPipManager.getPlaybackState();
- if (state == PLAYBACK_STATE_UNAVAILABLE) {
+ if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) {
mPlayPauseButtonView.setVisibility(View.GONE);
} else {
mPlayPauseButtonView.setVisibility(View.VISIBLE);
- if (state == PLAYBACK_STATE_PLAYING) {
+ if (state == PipManager.PLAYBACK_STATE_PLAYING) {
mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white_24dp);
mPlayPauseButtonView.setText(R.string.pip_pause);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
rename to packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 75c2bcd..5ee825c 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.tv.pip;
+package com.android.systemui.pip.tv;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackInfo;
@@ -25,7 +25,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Rect;
import android.media.session.MediaController;
@@ -33,17 +32,12 @@
import android.media.session.PlaybackState;
import android.os.Debug;
import android.os.Handler;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.IWindowManager;
-import android.view.InputChannel;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
-import android.view.WindowManagerGlobal;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -53,7 +47,6 @@
import java.util.List;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.view.WindowManager.INPUT_CONSUMER_PIP;
import static com.android.systemui.Prefs.Key.TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN;
/**
@@ -67,8 +60,6 @@
private static PipManager sPipManager;
- private static final int MAX_RUNNING_TASKS_COUNT = 10;
-
/**
* List of package and class name which are considered as Settings,
* so PIP location should be adjusted to the left of the side panel.
@@ -163,9 +154,6 @@
private boolean mOnboardingShown;
private String[] mLastPackagesResourceGranted;
- private InputChannel mInputChannel;
- private PipInputEventReceiver mInputEventReceiver;
-
private final Runnable mResizePinnedStackRunnable = new Runnable() {
@Override
public void run() {
@@ -203,25 +191,6 @@
}
};
- /**
- * Input handler used for Pip windows. Currently eats all the input events.
- */
- private final class PipInputEventReceiver extends InputEventReceiver {
- public PipInputEventReceiver(InputChannel inputChannel, Looper looper) {
- super(inputChannel, looper);
- }
-
- @Override
- public void onInputEvent(InputEvent event) {
- boolean handled = true;
- try {
- // To be implemented for input handling over Pip windows
- } finally {
- finishInputEvent(event, handled);
- }
- }
- }
-
private PipManager() { }
/**
@@ -246,26 +215,15 @@
mPipRecentsOverlayManager = new PipRecentsOverlayManager(context);
mMediaSessionManager =
(MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
-
- PackageManager pm = mContext.getPackageManager();
- if (!pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)) {
- // Initialize the Pip input consumer
- mInputChannel = new InputChannel();
- try {
- IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- wm.destroyInputConsumer(INPUT_CONSUMER_PIP);
- wm.createInputConsumer(INPUT_CONSUMER_PIP, mInputChannel);
- mInputEventReceiver = new PipInputEventReceiver(mInputChannel, Looper.myLooper());
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to create Pip input consumer", e);
- }
- }
}
private void loadConfigurationsAndApply() {
Resources res = mContext.getResources();
- mDefaultPipBounds = Rect.unflattenFromString(res.getString(
- com.android.internal.R.string.config_defaultPictureInPictureBounds));
+ try {
+ mDefaultPipBounds = mActivityManager.getDefaultPictureInPictureBounds();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get default PIP bounds", e);
+ }
mSettingsPipBounds = Rect.unflattenFromString(res.getString(
R.string.pip_settings_bounds));
mMenuModePipBounds = Rect.unflattenFromString(res.getString(
@@ -287,7 +245,7 @@
/**
* Updates the PIP per configuration changed.
*/
- void onConfigurationChanged() {
+ public void onConfigurationChanged() {
loadConfigurationsAndApply();
mPipRecentsOverlayManager.onConfigurationChanged(mContext);
}
@@ -347,11 +305,7 @@
*/
private void showPipOverlay() {
if (DEBUG) Log.d(TAG, "showPipOverlay()");
- // Temporary workaround to prevent the overlay on phones
- PackageManager pm = mContext.getPackageManager();
- if (pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)) {
- PipOverlayActivity.showPipOverlay(mContext);
- }
+ PipOverlayActivity.showPipOverlay(mContext);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
rename to packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
index 542a935..01d86b6 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.tv.pip;
+package com.android.systemui.pip.tv;
import android.animation.Animator;
import android.animation.AnimatorInflater;
@@ -23,7 +23,6 @@
import android.view.View;
import com.android.systemui.R;
-import com.android.systemui.Interpolators;
/**
* Activity to show the PIP menu to control PIP.
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipOnboardingActivity.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java
rename to packages/SystemUI/src/com/android/systemui/pip/tv/PipOnboardingActivity.java
index 9a87cfc..57952f4 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipOnboardingActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.tv.pip;
+package com.android.systemui.pip.tv;
import android.animation.Animator;
import android.animation.AnimatorInflater;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipOverlayActivity.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
rename to packages/SystemUI/src/com/android/systemui/pip/tv/PipOverlayActivity.java
index 011e159..f52121f 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipOverlayActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.tv.pip;
+package com.android.systemui.pip.tv;
import android.animation.Animator;
import android.animation.AnimatorInflater;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipRecentsControlsView.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsControlsView.java
rename to packages/SystemUI/src/com/android/systemui/pip/tv/PipRecentsControlsView.java
index ffe96afa..a891d12 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsControlsView.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipRecentsControlsView.java
@@ -14,26 +14,20 @@
* limitations under the License.
*/
-package com.android.systemui.tv.pip;
+package com.android.systemui.pip.tv;
import android.animation.Animator;
import android.animation.AnimatorInflater;
-import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
-import android.view.View.OnFocusChangeListener;
import android.widget.FrameLayout;
import com.android.systemui.R;
-import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_PLAYING;
-import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_PAUSED;
-import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_UNAVAILABLE;
-
/**
* An FrameLayout that contains {@link PipControlsView} with its scrim.
*/
@@ -49,7 +43,6 @@
}
private final PipManager mPipManager = PipManager.getInstance();
- private Listener mListener;
private PipControlsView mPipControlsView;
private View mScrim;
private Animator mFocusGainAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsOverlayManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipRecentsOverlayManager.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsOverlayManager.java
rename to packages/SystemUI/src/com/android/systemui/pip/tv/PipRecentsOverlayManager.java
index 895b8a2..835bcbc 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsOverlayManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipRecentsOverlayManager.java
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.tv.pip;
+package com.android.systemui.pip.tv;
import android.content.Context;
import android.graphics.PixelFormat;
-import android.graphics.Rect;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -32,9 +31,6 @@
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.Gravity.TOP;
import static android.view.View.MeasureSpec.UNSPECIFIED;
-import static com.android.systemui.tv.pip.PipManager.STATE_PIP_OVERLAY;
-import static com.android.systemui.tv.pip.PipManager.STATE_PIP_RECENTS;
-import static com.android.systemui.tv.pip.PipManager.STATE_PIP_RECENTS_FOCUSED;
public class PipRecentsOverlayManager {
private static final String TAG = "PipRecentsOverlayManager";
@@ -158,7 +154,7 @@
mIsPipFocusedInRecent = true;
mPipControlsView.startFocusGainAnimation();
mWindowManager.updateViewLayout(mOverlayView, mPipRecentsControlsViewFocusedLayoutParams);
- mPipManager.resizePinnedStack(STATE_PIP_RECENTS_FOCUSED);
+ mPipManager.resizePinnedStack(PipManager.STATE_PIP_RECENTS_FOCUSED);
if (mTalkBackEnabled) {
mPipControlsView.requestFocus();
mPipControlsView.sendAccessibilityEvent(
@@ -177,7 +173,7 @@
mIsPipFocusedInRecent = false;
mPipControlsView.startFocusLossAnimation();
mWindowManager.updateViewLayout(mOverlayView, mPipRecentsControlsViewLayoutParams);
- mPipManager.resizePinnedStack(STATE_PIP_RECENTS);
+ mPipManager.resizePinnedStack(PipManager.STATE_PIP_RECENTS);
if (mCallback != null) {
mCallback.onRecentsFocused();
}
@@ -198,7 +194,7 @@
}
mIsRecentsShown = true;
mIsPipFocusedInRecent = true;
- mPipManager.resizePinnedStack(STATE_PIP_RECENTS_FOCUSED);
+ mPipManager.resizePinnedStack(PipManager.STATE_PIP_RECENTS_FOCUSED);
// Overlay view will be added after the resize animation ends, if any.
}
@@ -212,7 +208,7 @@
removePipRecentsOverlayView();
if (mPipManager.isPipShown()) {
- mPipManager.resizePinnedStack(STATE_PIP_OVERLAY);
+ mPipManager.resizePinnedStack(PipManager.STATE_PIP_OVERLAY);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 4ac629d..ccb28e9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -188,7 +188,8 @@
profileOwner, profileVpn, primaryVpn);
} else {
if (isBranded) {
- return mContext.getString(R.string.branded_monitoring_description_app_personal);
+ return mContext.getString(R.string.branded_monitoring_description_app_personal,
+ primaryVpn);
} else {
return mContext.getString(R.string.monitoring_description_app_personal,
primaryVpn);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index e0cdb1a..d5218ed 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -79,6 +79,8 @@
import com.android.internal.app.AssistUtils;
import com.android.internal.os.BackgroundThread;
import com.android.systemui.R;
+import com.android.systemui.pip.tv.PipMenuActivity;
+import com.android.systemui.pip.tv.PipOnboardingActivity;
import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.RecentsImpl;
import com.android.systemui.recents.model.Task;
@@ -109,8 +111,8 @@
final static List<String> sRecentsBlacklist;
static {
sRecentsBlacklist = new ArrayList<>();
- sRecentsBlacklist.add("com.android.systemui.tv.pip.PipOnboardingActivity");
- sRecentsBlacklist.add("com.android.systemui.tv.pip.PipMenuActivity");
+ sRecentsBlacklist.add(PipOnboardingActivity.class.getName());
+ sRecentsBlacklist.add(PipMenuActivity.class.getName());
}
private static SystemServicesProxy sSystemServicesProxy;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
index ecb12d3..a2a8199 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
@@ -58,8 +58,8 @@
import com.android.systemui.recents.tv.views.TaskStackHorizontalGridView;
import com.android.systemui.recents.tv.views.TaskStackHorizontalViewAdapter;
import com.android.systemui.statusbar.BaseStatusBar;
-import com.android.systemui.tv.pip.PipManager;
-import com.android.systemui.tv.pip.PipRecentsOverlayManager;
+import com.android.systemui.pip.tv.PipManager;
+import com.android.systemui.pip.tv.PipRecentsOverlayManager;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
index ef9de53..ac9a217 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
@@ -25,7 +25,6 @@
import android.os.SystemClock;
import android.os.UserHandle;
-import com.android.systemui.SystemUIApplication;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
@@ -37,8 +36,7 @@
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.model.ThumbnailData;
import com.android.systemui.recents.tv.views.TaskCardView;
-import com.android.systemui.statusbar.tv.TvStatusBar;
-import com.android.systemui.tv.pip.PipManager;
+import com.android.systemui.pip.tv.PipManager;
public class RecentsTvImpl extends RecentsImpl{
public final static String RECENTS_TV_ACTIVITY =
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index f7d61835..936354e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -682,13 +682,13 @@
for (int i = 0; i < taskViewCount; i++) {
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
- int taskIndex = mStack.indexOfStackTask(task);
- TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex);
if (mIgnoreTasks.contains(task.key)) {
continue;
}
+ int taskIndex = mStack.indexOfStackTask(task);
+ TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex);
if (animationOverrides != null && animationOverrides.containsKey(task)) {
animation = animationOverrides.get(task);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index 583a63e..0f800bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -31,6 +31,7 @@
import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
+import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.tuner.TunerService;
@@ -42,7 +43,7 @@
* Class to detect gestures on the navigation bar.
*/
public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureListener
- implements TunerService.Tunable {
+ implements TunerService.Tunable, GestureHelper {
private static final String KEY_DOCK_WINDOW_GESTURE = "overview_nav_bar_gesture";
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 3a0eb94..798d9df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -44,16 +44,20 @@
import android.view.WindowManagerGlobal;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
-import android.widget.LinearLayout;
+
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.statusbar.phone.NavGesture;
+import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.policy.DeadZone;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-public class NavigationBarView extends FrameLayout {
+public class NavigationBarView extends FrameLayout implements PluginListener<NavGesture> {
final static boolean DEBUG = false;
final static String TAG = "StatusBar/NavBarView";
@@ -83,7 +87,7 @@
private Drawable mImeIcon;
private Drawable mMenuIcon;
- private NavigationBarGestureHelper mGestureHelper;
+ private GestureHelper mGestureHelper;
private DeadZone mDeadZone;
private final NavigationBarTransitions mBarTransitions;
@@ -105,6 +109,8 @@
private Configuration mConfiguration;
private NavigationBarInflaterView mNavigationInflaterView;
+ private RecentsComponent mRecentsComponent;
+ private Divider mDivider;
private class NavTransitionListener implements TransitionListener {
private boolean mBackTransitioning;
@@ -212,7 +218,12 @@
}
public void setComponents(RecentsComponent recentsComponent, Divider divider) {
- mGestureHelper.setComponents(recentsComponent, divider, this);
+ mRecentsComponent = recentsComponent;
+ mDivider = divider;
+ if (mGestureHelper instanceof NavigationBarGestureHelper) {
+ ((NavigationBarGestureHelper) mGestureHelper).setComponents(
+ recentsComponent, divider, this);
+ }
}
public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
@@ -700,6 +711,33 @@
}
}
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ PluginManager.getInstance(getContext()).addPluginListener(NavGesture.ACTION, this,
+ NavGesture.VERSION, false /* Only one */);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ PluginManager.getInstance(getContext()).removePluginListener(this);
+ }
+
+ @Override
+ public void onPluginConnected(NavGesture plugin) {
+ mGestureHelper = plugin.getGestureHelper();
+ updateTaskSwitchHelper();
+ }
+
+ @Override
+ public void onPluginDisconnected(NavGesture plugin) {
+ NavigationBarGestureHelper defaultHelper = new NavigationBarGestureHelper(getContext());
+ defaultHelper.setComponents(mRecentsComponent, mDivider, this);
+ mGestureHelper = defaultHelper;
+ updateTaskSwitchHelper();
+ }
+
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("NavigationBarView {");
final Rect r = new Rect();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 4b46578..da58d9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -1888,24 +1888,34 @@
}
updateBackgroundBounds();
if (!mCurrentBounds.equals(mBackgroundBounds)) {
- if (mAnimateNextBackgroundTop || mAnimateNextBackgroundBottom || areBoundsAnimating()) {
+ boolean animate = mAnimateNextBackgroundTop || mAnimateNextBackgroundBottom
+ || areBoundsAnimating();
+ if (!isExpanded()) {
+ abortBackgroundAnimators();
+ animate = false;
+ }
+ if (animate) {
startBackgroundAnimation();
} else {
mCurrentBounds.set(mBackgroundBounds);
applyCurrentBackgroundBounds();
}
} else {
- if (mBottomAnimator != null) {
- mBottomAnimator.cancel();
- }
- if (mTopAnimator != null) {
- mTopAnimator.cancel();
- }
+ abortBackgroundAnimators();
}
mAnimateNextBackgroundBottom = false;
mAnimateNextBackgroundTop = false;
}
+ private void abortBackgroundAnimators() {
+ if (mBottomAnimator != null) {
+ mBottomAnimator.cancel();
+ }
+ if (mTopAnimator != null) {
+ mTopAnimator.cancel();
+ }
+ }
+
private boolean areBoundsAnimating() {
return mBottomAnimator != null || mTopAnimator != null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 3c83921..f5c60a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -27,7 +27,7 @@
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.tv.pip.PipManager;
+import com.android.systemui.pip.tv.PipManager;
/**
* Status bar implementation for "large screen" products that mostly present no on-screen nav
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java
deleted file mode 100644
index 3306cb3..0000000
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2016 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.tv.pip;
-
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-
-import com.android.systemui.SystemUI;
-
-import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
-
-/**
- * Controls the picture-in-picture window.
- */
-public class PipUI extends SystemUI {
- private boolean mSupportPip;
-
- @Override
- public void start() {
- PackageManager pm = mContext.getPackageManager();
- mSupportPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
- if (!mSupportPip) {
- return;
- }
- PipManager pipManager = PipManager.getInstance();
- pipManager.initialize(mContext);
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- if (!mSupportPip) {
- return;
- }
- PipManager.getInstance().onConfigurationChanged();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java
new file mode 100644
index 0000000..fccb2a2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 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.keyguard;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import static junit.framework.Assert.*;
+import static org.mockito.Mockito.mock;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KeyguardMessageAreaTest extends SysuiTestCase {
+ private Context mContext = InstrumentationRegistry.getTargetContext();
+ private Handler mHandler = new Handler(Looper.getMainLooper());
+ private KeyguardMessageArea mMessageArea;
+
+ @Before
+ public void setUp() throws Exception {
+ KeyguardUpdateMonitor monitor = mock(KeyguardUpdateMonitor.class);
+ mHandler.post(()-> mMessageArea = new KeyguardMessageArea(mContext, null, monitor));
+ waitForIdleSync();
+ }
+
+ @Test
+ public void clearFollowedByMessage_keepsMessage() {
+ mHandler.post(()-> {
+ mMessageArea.setMessage("");
+ mMessageArea.setMessage("test");
+ });
+
+ waitForIdleSync();
+
+ CharSequence[] messageText = new CharSequence[1];
+ mHandler.post(()-> {
+ messageText[0] = mMessageArea.getText();
+ });
+
+ waitForIdleSync();
+
+ assertEquals("test", messageText[0]);
+ }
+
+}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 517dbe3..cebde98 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -2578,6 +2578,21 @@
// OPEN Settings > Bluetooth > Attempt to connect to device that shows dialog
BLUETOOTH_DIALOG_FRAGMENT = 613;
+ // ACTION: Logs provisioning started by zero touch.
+ PROVISIONING_ENTRY_POINT_ZERO_TOUCH = 614;
+
+ // ACTION: Logs provisioning started by NFC bump.
+ PROVISIONING_ENTRY_POINT_NFC = 615;
+
+ // ACTION: Logs provisioning started using QR code.
+ PROVISIONING_ENTRY_POINT_QR_CODE = 616;
+
+ // ACTION: Logs provisioning started using adb.
+ PROVISIONING_ENTRY_POINT_ADB = 617;
+
+ // ACTION: Logs provisioning started by trusted source.
+ PROVISIONING_ENTRY_POINT_TRUSTED_SOURCE = 618;
+
// ---- End O Constants, all O constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 8424b39..9d3035a 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -21,8 +21,8 @@
import android.app.ActivityManagerNative;
import android.app.AlarmManager;
import android.app.AppGlobals;
+import android.app.ApplicationThreadConstants;
import android.app.IActivityManager;
-import android.app.IApplicationThread;
import android.app.IBackupAgent;
import android.app.PackageInstallObserver;
import android.app.PendingIntent;
@@ -2897,7 +2897,7 @@
try {
mWakelock.setWorkSource(new WorkSource(mCurrentPackage.applicationInfo.uid));
agent = bindToAgentSynchronous(mCurrentPackage.applicationInfo,
- IApplicationThread.BACKUP_MODE_INCREMENTAL);
+ ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
addBackupTrace("agent bound; a? = " + (agent != null));
if (agent != null) {
mAgentBinder = agent;
@@ -3808,7 +3808,7 @@
Slog.d(TAG, "Binding to full backup agent : " + mPkg.packageName);
}
mAgent = bindToAgentSynchronous(mPkg.applicationInfo,
- IApplicationThread.BACKUP_MODE_FULL);
+ ApplicationThreadConstants.BACKUP_MODE_FULL);
}
return mAgent != null;
}
@@ -5533,7 +5533,7 @@
// All set; now set up the IPC and launch the agent
setUpPipes();
mAgent = bindToAgentSynchronous(mTargetApp,
- IApplicationThread.BACKUP_MODE_RESTORE_FULL);
+ ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL);
mAgentPackage = pkg;
} catch (IOException e) {
// fall through to error handling
@@ -6968,7 +6968,7 @@
// All set; now set up the IPC and launch the agent
setUpPipes();
mAgent = bindToAgentSynchronous(mTargetApp,
- IApplicationThread.BACKUP_MODE_RESTORE_FULL);
+ ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL);
mAgentPackage = pkg;
} catch (IOException e) {
// fall through to error handling
@@ -8390,7 +8390,7 @@
// Good to go! Set up and bind the agent...
mAgent = bindToAgentSynchronous(
mCurrentPackage.applicationInfo,
- IApplicationThread.BACKUP_MODE_INCREMENTAL);
+ ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
if (mAgent == null) {
Slog.w(TAG, "Can't find backup agent for " + packageName);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 3ad71ea..067f932 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -208,7 +208,7 @@
} finally {
mBluetoothLock.readLock().unlock();
}
- Slog.d(TAG, "state" + st);
+ Slog.d(TAG, "Airplane Mode change - current state: " + st);
if (isAirplaneModeOn()) {
// Clear registered LE apps to force shut-off
@@ -275,6 +275,7 @@
mContext.registerReceiver(mReceiver, filter);
loadStoredNameAndAddress();
if (isBluetoothPersistedStateOn()) {
+ if (DBG) Slog.d(TAG, "Startup: Bluetooth persisted state is ON.");
mEnableExternal = true;
}
@@ -301,8 +302,10 @@
* Returns true if the Bluetooth saved state is "on"
*/
private final boolean isBluetoothPersistedStateOn() {
- return Settings.Global.getInt(mContentResolver,
- Settings.Global.BLUETOOTH_ON, BLUETOOTH_ON_BLUETOOTH) != BLUETOOTH_OFF;
+ int state = Settings.Global.getInt(mContentResolver,
+ Settings.Global.BLUETOOTH_ON, -1);
+ if (DBG) Slog.d(TAG, "Bluetooth persisted state: " + state);
+ return state != BLUETOOTH_OFF;
}
/**
@@ -318,6 +321,7 @@
*
*/
private void persistBluetoothSetting(int value) {
+ if (DBG) Slog.d(TAG, "Persisting Bluetooth Setting: " + value);
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.BLUETOOTH_ON,
value);
@@ -1430,7 +1434,7 @@
{
int prevState = msg.arg1;
int newState = msg.arg2;
- if (DBG) Slog.d(TAG, "MESSAGE_BLUETOOTH_STATE_CHANGE: prevState = " + prevState + ", newState=" + newState);
+ if (DBG) Slog.d(TAG, "MESSAGE_BLUETOOTH_STATE_CHANGE: prevState = " + prevState + ", newState =" + newState);
mState = newState;
bluetoothStateChangeHandler(prevState, newState);
// handle error state transition case from TURNING_ON to OFF
@@ -1742,6 +1746,7 @@
private void bluetoothStateChangeHandler(int prevState, int newState) {
boolean isStandardBroadcast = true;
+ if (DBG) Slog.d(TAG, "bluetoothStateChangeHandler: " + prevState + " -> " + newState);
if (prevState != newState) {
//Notify all proxy objects first of adapter state change
if (newState == BluetoothAdapter.STATE_BLE_ON ||
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ae60d1e..de70026 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -132,6 +132,7 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.DataConnectionStats;
import com.android.server.connectivity.KeepaliveTracker;
+import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.LingerMonitor;
import com.android.server.connectivity.NetworkAgentInfo;
@@ -815,7 +816,8 @@
mTestMode = SystemProperties.get("cm.test.mode").equals("true")
&& SystemProperties.get("ro.build.type").equals("eng");
- mTethering = new Tethering(mContext, mNetd, statsService, mPolicyManager);
+ mTethering = new Tethering(mContext, mNetd, statsService, mPolicyManager,
+ IoThread.get().getLooper(), new MockableSystemProperties());
mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
@@ -2255,11 +2257,19 @@
synchronized (mNetworkForNetId) {
nai = mNetworkForNetId.get(netId);
}
- // If captive portal status has changed, update capabilities.
+ // If captive portal status has changed, update capabilities or disconnect.
if (nai != null && (visible != nai.lastCaptivePortalDetected)) {
final int oldScore = nai.getCurrentScore();
nai.lastCaptivePortalDetected = visible;
nai.everCaptivePortalDetected |= visible;
+ if (nai.lastCaptivePortalDetected &&
+ Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) {
+ if (DBG) log("Avoiding captive portal network: " + nai.name());
+ nai.asyncChannel.sendMessage(
+ NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT);
+ teardownUnneededNetwork(nai);
+ break;
+ }
updateCapabilities(oldScore, nai, nai.networkCapabilities);
}
if (!visible) {
@@ -2280,6 +2290,12 @@
return true;
}
+ private int getCaptivePortalMode() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.CAPTIVE_PORTAL_MODE,
+ Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
+ }
+
private boolean maybeHandleNetworkAgentInfoMessage(Message msg) {
switch (msg.what) {
default:
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 07aa5656..122074b 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -167,10 +167,17 @@
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra(Intent.EXTRA_DOCK_STATE, mReportedDockState);
+ boolean dockSoundsEnabled = Settings.Global.getInt(cr,
+ Settings.Global.DOCK_SOUNDS_ENABLED, 1) == 1;
+ boolean dockSoundsEnabledWhenAccessibility = Settings.Global.getInt(cr,
+ Settings.Global.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY, 1) == 1;
+ boolean accessibilityEnabled = Settings.Secure.getInt(cr,
+ Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
+
// Play a sound to provide feedback to confirm dock connection.
// Particularly useful for flaky contact pins...
- if (Settings.Global.getInt(cr,
- Settings.Global.DOCK_SOUNDS_ENABLED, 1) == 1) {
+ if ((dockSoundsEnabled) ||
+ (accessibilityEnabled && dockSoundsEnabledWhenAccessibility)) {
String whichSound = null;
if (mReportedDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
if ((previousDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index fb4b010..dc4a52d 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3070,7 +3070,7 @@
try {
TransferPipe tp = new TransferPipe();
try {
- thread.dumpService(tp.getWriteFd().getFileDescriptor(), r, args);
+ thread.dumpService(tp.getWriteFd(), r, args);
tp.setBufferPrefix(" ");
// Short timeout, since blocking here can
// deadlock with the application.
@@ -3338,7 +3338,7 @@
try {
TransferPipe tp = new TransferPipe();
try {
- r.app.thread.dumpService(tp.getWriteFd().getFileDescriptor(), r, args);
+ r.app.thread.dumpService(tp.getWriteFd(), r, args);
tp.setBufferPrefix(prefix + " ");
tp.go(fd);
} finally {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 097d2a5..1edc8d2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16,7 +16,11 @@
package com.android.server.am;
+import android.app.ApplicationThreadConstants;
import android.os.IDeviceIdentifiersPolicyService;
+import android.util.Size;
+import android.util.TypedValue;
+import android.view.DisplayInfo;
import com.android.internal.telephony.TelephonyIntents;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
@@ -82,7 +86,6 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.ApplicationErrorReport;
-import android.app.ApplicationThreadNative;
import android.app.BroadcastOptions;
import android.app.Dialog;
import android.app.IActivityContainer;
@@ -216,7 +219,6 @@
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.Xml;
-import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -253,6 +255,7 @@
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
+import static android.Manifest.permission.CHANGE_CONFIGURATION;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
@@ -276,6 +279,7 @@
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.res.Configuration.UI_MODE_TYPE_TELEVISION;
+import static android.os.Build.VERSION_CODES.N;
import static android.os.Process.PROC_CHAR;
import static android.os.Process.PROC_OUT_LONG;
import static android.os.Process.PROC_PARENS;
@@ -288,6 +292,9 @@
import static android.provider.Settings.Global.LENIENT_BACKGROUND_CHECK;
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
import static android.provider.Settings.System.FONT_SCALE;
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
@@ -1125,10 +1132,11 @@
private int mConfigurationSeq;
/**
- * Temp object used when global configuration is updated. It is also sent to outer world
- * instead of {@link #getGlobalConfiguration} because we don't trust anyone...
+ * Temp object used when global and/or display override configuration is updated. It is also
+ * sent to outer world instead of {@link #getGlobalConfiguration} because we don't trust
+ * anyone...
*/
- private Configuration mTempGlobalConfig = new Configuration();
+ private Configuration mTempConfig = new Configuration();
private final UpdateConfigurationResult mTmpUpdateConfigurationResult =
new UpdateConfigurationResult();
@@ -1369,7 +1377,6 @@
boolean mSupportsFreeformWindowManagement;
boolean mSupportsPictureInPicture;
boolean mSupportsLeanbackOnly;
- Rect mDefaultPinnedStackBounds;
IActivityController mController = null;
boolean mControllerIsAMonkey = false;
String mProfileApp = null;
@@ -1389,6 +1396,12 @@
final long[] mTmpLong = new long[2];
+ // The size and position information that describes where the pinned stack will go by default.
+ // In particular, the size is defined in DPs.
+ Size mDefaultPinnedStackSizeDp;
+ Size mDefaultPinnedStackScreenEdgeInsetsDp;
+ int mDefaultPinnedStackGravity;
+
static final class ProcessChangeItem {
static final int CHANGE_ACTIVITIES = 1<<0;
static final int CHANGE_PROCESS_STATE = 1<<1;
@@ -2707,14 +2720,14 @@
mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations"));
- mTempGlobalConfig.setToDefaults();
- mTempGlobalConfig.setLocales(LocaleList.getDefault());
- mConfigurationSeq = mTempGlobalConfig.seq = 1;
+ mTempConfig.setToDefaults();
+ mTempConfig.setLocales(LocaleList.getDefault());
+ mConfigurationSeq = mTempConfig.seq = 1;
mProcessCpuTracker.init();
mStackSupervisor = new ActivityStackSupervisor(this);
- mStackSupervisor.onConfigurationChanged(mTempGlobalConfig);
+ mStackSupervisor.onConfigurationChanged(mTempConfig);
mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);
mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
mActivityStarter = new ActivityStarter(this, mStackSupervisor);
@@ -4059,7 +4072,7 @@
@Override
public int getPackageProcessState(String packageName, String callingPackage) {
if (!hasUsageStatsPermission(callingPackage)) {
- enforceCallingPermission(android.Manifest.permission.GET_PACKAGE_IMPORTANCE,
+ enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
"getPackageProcessState");
}
@@ -4069,20 +4082,12 @@
final ProcessRecord proc = mLruProcesses.get(i);
if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT
|| procState > proc.setProcState) {
- boolean found = false;
- for (int j=proc.pkgList.size()-1; j>=0 && !found; j--) {
- if (proc.pkgList.keyAt(j).equals(packageName)) {
- procState = proc.setProcState;
- found = true;
- }
+ if (proc.pkgList.containsKey(packageName)) {
+ procState = proc.setProcState;
+ break;
}
- if (proc.pkgDeps != null && !found) {
- for (int j=proc.pkgDeps.size()-1; j>=0; j--) {
- if (proc.pkgDeps.valueAt(j).equals(packageName)) {
- procState = proc.setProcState;
- break;
- }
- }
+ if (proc.pkgDeps != null && proc.pkgDeps.contains(packageName)) {
+ procState = proc.setProcState;
}
}
}
@@ -4752,23 +4757,12 @@
if (r == null) {
return;
}
- TaskRecord task = r.task;
- if (task != null && (!task.mFullscreen || !task.getStack().mFullscreen)) {
- // Fixed screen orientation isn't supported when activities aren't in full screen
- // mode.
- return;
- }
final long origId = Binder.clearCallingIdentity();
- mWindowManager.setAppOrientation(r.appToken, requestedOrientation);
- Configuration config = mWindowManager.updateOrientationFromAppTokens(
- getGlobalConfiguration(), r.mayFreezeScreenLocked(r.app) ? r.appToken : null);
- if (config != null) {
- r.frozenBeforeDestroy = true;
- if (!updateConfigurationLocked(config, r, false)) {
- mStackSupervisor.resumeFocusedStackTopActivityLocked();
- }
+ try {
+ r.setRequestedOrientation(requestedOrientation);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
- Binder.restoreCallingIdentity(origId);
}
}
@@ -6507,11 +6501,11 @@
TAG, "New app record " + app
+ " thread=" + thread.asBinder() + " pid=" + pid);
try {
- int testMode = IApplicationThread.DEBUG_OFF;
+ int testMode = ApplicationThreadConstants.DEBUG_OFF;
if (mDebugApp != null && mDebugApp.equals(processName)) {
testMode = mWaitForDebugger
- ? IApplicationThread.DEBUG_WAIT
- : IApplicationThread.DEBUG_ON;
+ ? ApplicationThreadConstants.DEBUG_WAIT
+ : ApplicationThreadConstants.DEBUG_ON;
app.debugging = true;
if (mDebugTransient) {
mDebugApp = mOrigDebugApp;
@@ -7595,7 +7589,7 @@
// current bounds.
final ActivityStack pinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID);
final Rect bounds = (pinnedStack != null)
- ? pinnedStack.mBounds : mDefaultPinnedStackBounds;
+ ? pinnedStack.mBounds : getDefaultPictureInPictureBounds();
mStackSupervisor.moveActivityToPinnedStackLocked(
r, "enterPictureInPictureMode", bounds);
@@ -7605,6 +7599,85 @@
}
}
+ @Override
+ public Rect getDefaultPictureInPictureBounds() {
+ final long origId = Binder.clearCallingIdentity();
+ final Rect defaultBounds = new Rect();
+ try {
+ synchronized(this) {
+ if (!mSupportsPictureInPicture) {
+ return new Rect();
+ }
+
+ // Convert the sizes to for the current display state
+ final DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
+ final int stackWidth = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
+ mDefaultPinnedStackSizeDp.getWidth(), dm);
+ final int stackHeight = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
+ mDefaultPinnedStackSizeDp.getHeight(), dm);
+ final Rect maxBounds = new Rect();
+ getPictureInPictureBounds(maxBounds);
+ Gravity.apply(mDefaultPinnedStackGravity, stackWidth, stackHeight,
+ maxBounds, 0, 0, defaultBounds);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ return defaultBounds;
+ }
+
+ @Override
+ public Rect getPictureInPictureMovementBounds() {
+ final long origId = Binder.clearCallingIdentity();
+ final Rect maxBounds = new Rect();
+ try {
+ synchronized(this) {
+ if (!mSupportsPictureInPicture) {
+ return new Rect();
+ }
+
+ getPictureInPictureBounds(maxBounds);
+
+ // Adjust the max bounds by the current stack dimensions
+ final StackInfo pinnedStackInfo = mStackSupervisor.getStackInfoLocked(
+ PINNED_STACK_ID);
+ if (pinnedStackInfo != null) {
+ maxBounds.right = Math.max(maxBounds.left, maxBounds.right -
+ pinnedStackInfo.bounds.width());
+ maxBounds.bottom = Math.max(maxBounds.top, maxBounds.bottom -
+ pinnedStackInfo.bounds.height());
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ return maxBounds;
+ }
+
+ /**
+ * Calculate the bounds where the pinned stack can move in the current display state.
+ */
+ private void getPictureInPictureBounds(Rect outRect) {
+ // Convert the insets to for the current display state
+ final DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
+ final int insetsLR = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
+ mDefaultPinnedStackScreenEdgeInsetsDp.getWidth(), dm);
+ final int insetsTB = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
+ mDefaultPinnedStackScreenEdgeInsetsDp.getHeight(), dm);
+ try {
+ final Rect insets = new Rect();
+ final DisplayInfo info = mStackSupervisor.getDisplayInfo(DEFAULT_DISPLAY);
+ mWindowManager.getStableInsets(insets);
+
+ // Calculate the insets from the system decorations and apply the gravity
+ outRect.set(insets.left + insetsLR, insets.top + insetsTB,
+ info.logicalWidth - insets.right - insetsLR,
+ info.logicalHeight - insets.bottom - insetsTB);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to calculate PIP movement bounds", e);
+ }
+ }
+
// =========================================================
// PROCESS INFO
// =========================================================
@@ -9811,7 +9884,7 @@
if (stack != null && stack.mActivityContainer.isAttachedLocked()) {
return stack.mActivityContainer.getDisplayId();
}
- return Display.DEFAULT_DISPLAY;
+ return DEFAULT_DISPLAY;
}
}
@@ -13174,8 +13247,12 @@
com.android.internal.R.dimen.thumbnail_width);
mThumbnailHeight = res.getDimensionPixelSize(
com.android.internal.R.dimen.thumbnail_height);
- mDefaultPinnedStackBounds = Rect.unflattenFromString(res.getString(
- com.android.internal.R.string.config_defaultPictureInPictureBounds));
+ mDefaultPinnedStackSizeDp = Size.parseSize(res.getString(
+ com.android.internal.R.string.config_defaultPictureInPictureSize));
+ mDefaultPinnedStackScreenEdgeInsetsDp = Size.parseSize(res.getString(
+ com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets));
+ mDefaultPinnedStackGravity = res.getInteger(
+ com.android.internal.R.integer.config_defaultPictureInPictureGravity);
mAppErrors.loadAppsNotReportingCrashesFromConfigLocked(res.getString(
com.android.internal.R.string.config_appsNotReportingCrashes));
mUserController.mUserSwitchUiEnabled = !res.getBoolean(
@@ -14257,6 +14334,11 @@
}
} else if ("locks".equals(cmd)) {
LockGuard.dump(fd, pw, args);
+ } else if ("pip".equals(cmd)) {
+ pw.print("defaultBounds="); getDefaultPictureInPictureBounds().printShortString(pw);
+ pw.println();
+ pw.print("movementBounds="); getPictureInPictureMovementBounds().printShortString(pw);
+ pw.println();
} else {
// Dumping a single activity?
if (!dumpActivity(fd, pw, cmd, args, opti, dumpAll, dumpVisibleStacks)) {
@@ -14779,6 +14861,7 @@
}
if (dumpPackage == null) {
pw.println(" mGlobalConfiguration: " + getGlobalConfiguration());
+ mStackSupervisor.dumpDisplayConfigs(pw, " ");
}
if (dumpAll) {
pw.println(" mConfigWillChange: " + getFocusedStack().mConfigWillChange);
@@ -15155,7 +15238,8 @@
if (lastTask != r.task) {
lastTask = r.task;
pw.print("TASK "); pw.print(lastTask.affinity);
- pw.print(" id="); pw.println(lastTask.taskId);
+ pw.print(" id="); pw.print(lastTask.taskId);
+ pw.print(" userId="); pw.println(lastTask.userId);
if (dumpAll) {
lastTask.dump(pw, " ");
}
@@ -15190,7 +15274,7 @@
try {
TransferPipe tp = new TransferPipe();
try {
- r.app.thread.dumpActivity(tp.getWriteFd().getFileDescriptor(),
+ r.app.thread.dumpActivity(tp.getWriteFd(),
r.appToken, innerPrefix, args);
tp.go(fd);
} finally {
@@ -15723,7 +15807,7 @@
try {
TransferPipe tp = new TransferPipe();
try {
- r.thread.dumpGfxInfo(tp.getWriteFd().getFileDescriptor(), args);
+ r.thread.dumpGfxInfo(tp.getWriteFd(), args);
tp.go(fd);
} finally {
tp.kill();
@@ -15756,7 +15840,7 @@
try {
TransferPipe tp = new TransferPipe();
try {
- r.thread.dumpDbInfo(tp.getWriteFd().getFileDescriptor(), args);
+ r.thread.dumpDbInfo(tp.getWriteFd(), args);
tp.go(fd);
} finally {
tp.kill();
@@ -16160,10 +16244,22 @@
pw.println();
}
} else {
+ pw.flush();
try {
- pw.flush();
- thread.dumpMemInfo(fd, mi, isCheckinRequest, dumpFullDetails,
- dumpDalvik, dumpSummaryOnly, dumpUnreachable, innerArgs);
+ TransferPipe tp = new TransferPipe();
+ try {
+ thread.dumpMemInfo(tp.getWriteFd(),
+ mi, isCheckinRequest, dumpFullDetails,
+ dumpDalvik, dumpSummaryOnly, dumpUnreachable, innerArgs);
+ tp.go(fd);
+ } finally {
+ tp.kill();
+ }
+ } catch (IOException e) {
+ if (!isCheckinRequest) {
+ pw.println("Got IoException!");
+ pw.flush();
+ }
} catch (RemoteException e) {
if (!isCheckinRequest) {
pw.println("Got RemoteException!");
@@ -17333,9 +17429,10 @@
}
BackupRecord r = new BackupRecord(ss, app, backupMode);
- ComponentName hostingName = (backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL)
- ? new ComponentName(app.packageName, app.backupAgentName)
- : new ComponentName("android", "FullBackupAgent");
+ ComponentName hostingName =
+ (backupMode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL)
+ ? new ComponentName(app.packageName, app.backupAgentName)
+ : new ComponentName("android", "FullBackupAgent");
// startProcessLocked() returns existing proc's record if it's already running
ProcessRecord proc = startProcessLocked(app.processName, app,
false, 0, "backup", hostingName, false, false, false);
@@ -17348,7 +17445,8 @@
// process, etc, then mark it as being in full backup so that certain calls to the
// process can be blocked. This is not reset to false anywhere because we kill the
// process after the full backup is done and the ProcessRecord will vaporize anyway.
- if (UserHandle.isApp(app.uid) && backupMode == IApplicationThread.BACKUP_MODE_FULL) {
+ if (UserHandle.isApp(app.uid) &&
+ backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL) {
proc.inFullBackup = true;
}
r.app = proc;
@@ -18019,8 +18117,8 @@
}
mRecentTasks.cleanupLocked(UserHandle.USER_ALL);
sendPackageBroadcastLocked(
- IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE, list,
- userId);
+ ApplicationThreadConstants.EXTERNAL_STORAGE_UNAVAILABLE,
+ list, userId);
}
break;
case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
@@ -18045,8 +18143,8 @@
removed ? "pkg removed" : "pkg changed");
}
final int cmd = killProcess
- ? IApplicationThread.PACKAGE_REMOVED
- : IApplicationThread.PACKAGE_REMOVED_DONT_KILL;
+ ? ApplicationThreadConstants.PACKAGE_REMOVED
+ : ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL;
sendPackageBroadcastLocked(cmd,
new String[] {ssp}, userId);
if (fullUninstall) {
@@ -18111,7 +18209,7 @@
return ActivityManager.BROADCAST_SUCCESS;
}
mStackSupervisor.updateActivityApplicationInfoLocked(aInfo);
- sendPackageBroadcastLocked(IApplicationThread.PACKAGE_REPLACED,
+ sendPackageBroadcastLocked(ApplicationThreadConstants.PACKAGE_REPLACED,
new String[] {ssp}, userId);
}
break;
@@ -18826,8 +18924,7 @@
@Override
public void updatePersistentConfiguration(Configuration values) {
- enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
- "updatePersistentConfiguration()");
+ enforceCallingPermission(CHANGE_CONFIGURATION, "updatePersistentConfiguration()");
enforceWriteSettingsPermission("updatePersistentConfiguration()");
if (values == null) {
throw new NullPointerException("Configuration must not be null");
@@ -18852,12 +18949,16 @@
private void updateFontScaleIfNeeded(@UserIdInt int userId) {
final float scaleFactor = Settings.System.getFloatForUser(mContext.getContentResolver(),
FONT_SCALE, 1.0f, userId);
- if (getGlobalConfiguration().fontScale != scaleFactor) {
- final Configuration configuration = mWindowManager.computeNewConfiguration();
- configuration.fontScale = scaleFactor;
- synchronized (this) {
- updatePersistentConfigurationLocked(configuration, userId);
+
+ synchronized (this) {
+ if (getGlobalConfiguration().fontScale == scaleFactor) {
+ return;
}
+
+ final Configuration configuration
+ = mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY);
+ configuration.fontScale = scaleFactor;
+ updatePersistentConfigurationLocked(configuration, userId);
}
}
@@ -18882,21 +18983,20 @@
@Override
public boolean updateConfiguration(Configuration values) {
- enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
- "updateConfiguration()");
+ enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()");
synchronized(this) {
if (values == null && mWindowManager != null) {
// sentinel: fetch the current configuration from the window manager
- values = mWindowManager.computeNewConfiguration();
+ values = mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY);
}
if (mWindowManager != null) {
+ // Update OOM levels based on display size.
mProcessList.applyDisplaySize(mWindowManager);
}
final long origId = Binder.clearCallingIdentity();
-
try {
if (values != null) {
Settings.System.clearConfiguration(values);
@@ -18983,8 +19083,8 @@
/** Update default (global) configuration and notify listeners about changes. */
private int updateGlobalConfiguration(@NonNull Configuration values, boolean initLocale,
boolean persistent, int userId, boolean deferResume) {
- mTempGlobalConfig.setTo(getGlobalConfiguration());
- final int changes = mTempGlobalConfig.updateFrom(values);
+ mTempConfig.setTo(getGlobalConfiguration());
+ final int changes = mTempConfig.updateFrom(values);
if (changes == 0) {
return 0;
}
@@ -19011,33 +19111,33 @@
}
mConfigurationSeq = Math.max(++mConfigurationSeq, 1);
- mTempGlobalConfig.seq = mConfigurationSeq;
+ mTempConfig.seq = mConfigurationSeq;
// Update stored global config and notify everyone about the change.
- mStackSupervisor.onConfigurationChanged(mTempGlobalConfig);
+ mStackSupervisor.onConfigurationChanged(mTempConfig);
- Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempGlobalConfig);
+ Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
// TODO(multi-display): Update UsageEvents#Event to include displayId.
- mUsageStatsService.reportConfigurationChange(mTempGlobalConfig,
+ mUsageStatsService.reportConfigurationChange(mTempConfig,
mUserController.getCurrentUserIdLocked());
// TODO: If our config changes, should we auto dismiss any currently showing dialogs?
- mShowDialogs = shouldShowDialogs(mTempGlobalConfig, mInVrMode);
+ mShowDialogs = shouldShowDialogs(mTempConfig, mInVrMode);
AttributeCache ac = AttributeCache.instance();
if (ac != null) {
- ac.updateConfiguration(mTempGlobalConfig);
+ ac.updateConfiguration(mTempConfig);
}
// Make sure all resources in our process are updated right now, so that anyone who is going
// to retrieve resource values after we return will be sure to get the new ones. This is
// especially important during boot, where the first config change needs to guarantee all
// resources have that config before following boot code is executed.
- mSystemThread.applyConfigurationToResources(mTempGlobalConfig);
+ mSystemThread.applyConfigurationToResources(mTempConfig);
// We need another copy of global config because we're scheduling some calls instead of
// running them in place. We need to be sure that object we send will be handled unchanged.
- final Configuration configCopy = new Configuration(mTempGlobalConfig);
+ final Configuration configCopy = new Configuration(mTempConfig);
if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
msg.obj = configCopy;
@@ -19045,16 +19145,6 @@
mHandler.sendMessage(msg);
}
- // TODO(multi-display): Clear also on secondary display density change?
- final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
- if (isDensityChange) {
- // Reset the unsupported display size dialog.
- mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG);
-
- killAllBackgroundProcessesExcept(Build.VERSION_CODES.N,
- ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
- }
-
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord app = mLruProcesses.get(i);
try {
@@ -19084,13 +19174,116 @@
UserHandle.USER_ALL);
}
+ // Override configuration of the default display duplicates global config, so we need to
+ // update it also. This will also notify WindowManager about changes.
+ performDisplayOverrideConfigUpdate(mStackSupervisor.getConfiguration(), deferResume,
+ DEFAULT_DISPLAY);
+
+ return changes;
+ }
+
+ @Override
+ public boolean updateDisplayOverrideConfiguration(Configuration values, int displayId) {
+ enforceCallingPermission(CHANGE_CONFIGURATION, "updateDisplayOverrideConfiguration()");
+
+ synchronized (this) {
+ if (values == null && mWindowManager != null) {
+ // sentinel: fetch the current configuration from the window manager
+ values = mWindowManager.computeNewConfiguration(displayId);
+ }
+
+ if (mWindowManager != null) {
+ // Update OOM levels based on display size.
+ mProcessList.applyDisplaySize(mWindowManager);
+ }
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ if (values != null) {
+ Settings.System.clearConfiguration(values);
+ }
+ updateDisplayOverrideConfigurationLocked(values, null /* starting */,
+ false /* deferResume */, displayId, mTmpUpdateConfigurationResult);
+ return mTmpUpdateConfigurationResult.changes != 0;
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+
+ boolean updateDisplayOverrideConfigurationLocked(Configuration values, ActivityRecord starting,
+ boolean deferResume, int displayId) {
+ return updateDisplayOverrideConfigurationLocked(values, starting, deferResume /* deferResume */,
+ displayId, null /* result */);
+ }
+
+ /**
+ * Updates override configuration specific for the selected display. If no config is provided,
+ * new one will be computed in WM based on current display info.
+ */
+ private boolean updateDisplayOverrideConfigurationLocked(Configuration values,
+ ActivityRecord starting, boolean deferResume, int displayId,
+ UpdateConfigurationResult result) {
+ int changes = 0;
+ boolean kept = true;
+
+ if (mWindowManager != null) {
+ mWindowManager.deferSurfaceLayout();
+ }
+ try {
+ if (values != null) {
+ if (displayId == DEFAULT_DISPLAY) {
+ // Override configuration of the default display duplicates global config, so
+ // we're calling global config update instead for default display. It will also
+ // apply the correct override config.
+ changes = updateGlobalConfiguration(values, false /* initLocale */,
+ false /* persistent */, UserHandle.USER_NULL /* userId */, deferResume);
+ } else {
+ changes = performDisplayOverrideConfigUpdate(values, deferResume, displayId);
+ }
+ }
+
+ kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
+ } finally {
+ if (mWindowManager != null) {
+ mWindowManager.continueSurfaceLayout();
+ }
+ }
+
+ if (result != null) {
+ result.changes = changes;
+ result.activityRelaunched = !kept;
+ }
+ return kept;
+ }
+
+ private int performDisplayOverrideConfigUpdate(Configuration values, boolean deferResume,
+ int displayId) {
+ mTempConfig.setTo(mStackSupervisor.getDisplayOverrideConfiguration(displayId));
+ final int changes = mTempConfig.updateFrom(values);
+ if (changes == 0) {
+ return 0;
+ }
+
+ Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " " + mTempConfig
+ + " for displayId=" + displayId);
+ mStackSupervisor.setDisplayOverrideConfiguration(mTempConfig, displayId);
+
+ final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
+ if (isDensityChange) {
+ // Reset the unsupported display size dialog.
+ mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG);
+
+ killAllBackgroundProcessesExcept(N, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ }
+
// Update the configuration with WM first and check if any of the stacks need to be resized
// due to the configuration change. If so, resize the stacks now and do any relaunches if
// necessary. This way we don't need to relaunch again afterwards in
// ensureActivityConfigurationLocked().
if (mWindowManager != null) {
final int[] resizedStacks =
- mWindowManager.setNewConfiguration(mTempGlobalConfig);
+ mWindowManager.setNewDisplayOverrideConfiguration(mTempConfig, displayId);
if (resizedStacks != null) {
for (int stackId : resizedStacks) {
resizeStackWithBoundsFromWindowManager(stackId, deferResume);
@@ -21881,8 +22074,7 @@
try {
TransferPipe tp = new TransferPipe();
try {
- process.thread.stopBinderTrackingAndDump(
- tp.getWriteFd().getFileDescriptor());
+ process.thread.stopBinderTrackingAndDump(tp.getWriteFd());
tp.go(fd.getFileDescriptor());
} finally {
tp.kill();
@@ -22206,7 +22398,7 @@
if (tr == null) {
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
- appThread = ApplicationThreadNative.asInterface(whoThread);
+ appThread = IApplicationThread.Stub.asInterface(whoThread);
if (appThread == null) {
throw new IllegalArgumentException("Bad app thread " + appThread);
}
@@ -22291,4 +22483,29 @@
// before the profile user is unlocked.
return rInfo != null && rInfo.activityInfo != null;
}
+
+ /**
+ * Attach an agent to the specified process (proces name or PID)
+ */
+ public void attachAgent(String process, String path) {
+ try {
+ synchronized (this) {
+ ProcessRecord proc = findProcessLocked(process, UserHandle.USER_SYSTEM, "attachAgent");
+ if (proc == null || proc.thread == null) {
+ throw new IllegalArgumentException("Unknown process: " + process);
+ }
+
+ boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
+ if (!isDebuggable) {
+ if ((proc.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
+ throw new SecurityException("Process not debuggable: " + proc);
+ }
+ }
+
+ proc.thread.attachAgent(path);
+ }
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Process disappeared");
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 7d9a706..7a692b6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -220,6 +220,8 @@
return runTask(pw);
case "write":
return runWrite(pw);
+ case "attach-agent":
+ return runAttachAgent(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -2462,6 +2464,21 @@
return 0;
}
+ int runAttachAgent(PrintWriter pw) {
+ // TODO: revisit the permissions required for attaching agents
+ mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
+ "attach-agent");
+ String process = getNextArgRequired();
+ String agent = getNextArgRequired();
+ String opt;
+ if ((opt = getNextArg()) != null) {
+ pw.println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ mInternal.attachAgent(process, agent);
+ return 0;
+ }
+
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
@@ -2481,6 +2498,7 @@
pw.println(" p[rocesses] [PACKAGE_NAME]: process state");
pw.println(" o[om]: out of memory management");
pw.println(" perm[issions]: URI permission grant state");
+ pw.println(" pip: PIP state");
pw.println(" prov[iders] [COMP_SPEC ...]: content provider state");
pw.println(" provider [COMP_SPEC]: provider client-side state");
pw.println(" s[ervices] [COMP_SPEC ...]: service state");
@@ -2618,6 +2636,8 @@
pw.println(" Optionally controls lenient background check mode, returns current mode.");
pw.println(" get-uid-state <UID>");
pw.println(" Gets the process state of an app given its <UID>.");
+ pw.println(" attach-agent <PROCESS> <FILE>");
+ pw.println(" Attach an agent to the specified <PROCESS>, which may be either a process name or a PID.");
pw.println(" get-config");
pw.println(" Rtrieve the configuration and any recent configurations of the device.");
pw.println(" suppress-resize-config-changes <true|false>");
@@ -2682,6 +2702,8 @@
pw.println(" Test command for sizing <TASK_ID> by <STEP_SIZE>");
pw.println(" increments within the screen applying the optional [DELAY_MS] between");
pw.println(" each step.");
+ pw.println(" pip");
+ pw.println(" Gets the current PIP state.");
pw.println(" write");
pw.println(" Write all pending state to storage.");
pw.println();
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index d89f02d..abe4f30 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1279,7 +1279,7 @@
stopFreezingScreenLocked(false);
try {
if (returningOptions != null) {
- app.thread.scheduleOnNewActivityOptions(appToken, returningOptions);
+ app.thread.scheduleOnNewActivityOptions(appToken, returningOptions.toBundle());
}
} catch(RemoteException e) {
}
@@ -1646,6 +1646,17 @@
return null;
}
+ /**
+ * @return display id to which this record is attached, -1 if not attached.
+ */
+ int getDisplayId() {
+ final ActivityStack stack = getStack();
+ if (stack == null) {
+ return -1;
+ }
+ return stack.mDisplayId;
+ }
+
final boolean isDestroyable() {
if (finishing || app == null || state == ActivityState.DESTROYING
|| state == ActivityState.DESTROYED) {
@@ -1704,6 +1715,26 @@
}
}
+ void setRequestedOrientation(int requestedOrientation) {
+ if (task != null && (!task.mFullscreen || !task.getStack().mFullscreen)) {
+ // Fixed screen orientation isn't supported when activities aren't in full screen mode.
+ return;
+ }
+
+ service.mWindowManager.setAppOrientation(appToken, requestedOrientation);
+ final int displayId = getDisplayId();
+ final Configuration config = service.mWindowManager.updateOrientationFromAppTokens(
+ mStackSupervisor.getDisplayOverrideConfiguration(displayId),
+ mayFreezeScreenLocked(app) ? appToken : null, displayId);
+ if (config != null) {
+ frozenBeforeDestroy = true;
+ if (!service.updateDisplayOverrideConfigurationLocked(config, this,
+ false /* deferResume */, displayId)) {
+ mStackSupervisor.resumeFocusedStackTopActivityLocked();
+ }
+ }
+ }
+
// TODO: now used only in one place to address race-condition. Remove when that will be fixed.
void setLastReportedConfiguration(@NonNull Configuration config) {
mLastReportedConfiguration.setTo(config);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 55066fd..22d8078 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2285,13 +2285,14 @@
// the screen based on the new activity order.
boolean notUpdated = true;
if (mStackSupervisor.isFocusedStack(this)) {
- Configuration config = mWindowManager.updateOrientationFromAppTokens(
- mService.getGlobalConfiguration(),
- next.mayFreezeScreenLocked(next.app) ? next.appToken : null);
+ final Configuration config = mWindowManager.updateOrientationFromAppTokens(
+ mStackSupervisor.getDisplayOverrideConfiguration(mDisplayId),
+ next.mayFreezeScreenLocked(next.app) ? next.appToken : null, mDisplayId);
if (config != null) {
next.frozenBeforeDestroy = true;
}
- notUpdated = !mService.updateConfigurationLocked(config, next, false);
+ notUpdated = !mService.updateDisplayOverrideConfigurationLocked(config, next,
+ false /* deferResume */, mDisplayId);
}
if (notUpdated) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index cd7481c..ca36908 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -419,7 +419,7 @@
}
@Override
- protected ConfigurationContainer getChildAt(int index) {
+ protected ActivityDisplay getChildAt(int index) {
return mActivityDisplays.valueAt(index);
}
@@ -428,6 +428,24 @@
return null;
}
+ Configuration getDisplayOverrideConfiguration(int displayId) {
+ final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+ if (activityDisplay == null) {
+ throw new IllegalArgumentException("No display found with id: " + displayId);
+ }
+
+ return activityDisplay.getOverrideConfiguration();
+ }
+
+ void setDisplayOverrideConfiguration(Configuration overrideConfiguration, int displayId) {
+ final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+ if (activityDisplay == null) {
+ throw new IllegalArgumentException("No display found with id: " + displayId);
+ }
+
+ activityDisplay.onOverrideConfigurationChanged(overrideConfiguration);
+ }
+
static class FindTaskResult {
ActivityRecord r;
boolean matchedByRootAffinity;
@@ -1190,20 +1208,20 @@
r.startLaunchTickingLocked();
}
- // Have the window manager re-evaluate the orientation of
- // the screen based on the new activity order. Note that
- // as a result of this, it can call back into the activity
- // manager with a new orientation. We don't care about that,
- // because the activity is not currently running so we are
- // just restarting it anyway.
+ // Have the window manager re-evaluate the orientation of the screen based on the new
+ // activity order. Note that as a result of this, it can call back into the activity
+ // manager with a new orientation. We don't care about that, because the activity is not
+ // currently running so we are just restarting it anyway.
if (checkConfig) {
- Configuration config = mWindowManager.updateOrientationFromAppTokens(
- mService.getGlobalConfiguration(),
- r.mayFreezeScreenLocked(app) ? r.appToken : null);
+ final int displayId = r.getDisplayId();
+ final Configuration config = mWindowManager.updateOrientationFromAppTokens(
+ getDisplayOverrideConfiguration(displayId),
+ r.mayFreezeScreenLocked(app) ? r.appToken : null, displayId);
// Deferring resume here because we're going to launch new activity shortly.
// We don't want to perform a redundant launch of the same record while ensuring
// configurations and trying to resume top activity of focused stack.
- mService.updateConfigurationLocked(config, r, false, true /* deferResume */);
+ mService.updateDisplayOverrideConfigurationLocked(config, r, true /* deferResume */,
+ displayId);
}
r.app = app;
@@ -3156,6 +3174,20 @@
}
/**
+ * Dump all connected displays' configurations.
+ * @param prefix Prefix to apply to each line of the dump.
+ */
+ void dumpDisplayConfigs(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.println("Display override configurations:");
+ final int displayCount = mActivityDisplays.size();
+ for (int i = 0; i < displayCount; i++) {
+ final ActivityDisplay activityDisplay = mActivityDisplays.valueAt(i);
+ pw.print(prefix); pw.print(" "); pw.print(activityDisplay.mDisplayId); pw.print(": ");
+ pw.println(activityDisplay.getOverrideConfiguration());
+ }
+ }
+
+ /**
* Dumps the activities matching the given {@param name} in the either the focused stack
* or all visible stacks if {@param dumpVisibleStacks} is true.
*/
@@ -3324,8 +3356,7 @@
try {
TransferPipe tp = new TransferPipe();
try {
- r.app.thread.dumpActivity(tp.getWriteFd().getFileDescriptor(),
- r.appToken, innerPrefix, args);
+ r.app.thread.dumpActivity(tp.getWriteFd(), r.appToken, innerPrefix, args);
// Short timeout, since blocking here can
// deadlock with the application.
tp.go(fd, 2000);
@@ -3394,6 +3425,10 @@
mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_CHANGED, displayId, 0));
}
+ DisplayInfo getDisplayInfo(int displayId) {
+ return mActivityDisplays.get(displayId).mDisplayInfo;
+ }
+
private void handleDisplayAdded(int displayId) {
boolean newDisplay;
synchronized (mService) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index a834af7..20a14d3 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1592,9 +1592,9 @@
== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
// The caller has requested to completely replace any existing task with its new
// activity. Well that should not be too hard...
+ intentActivity.task.performClearTaskLocked();
+ intentActivity.task.setIntent(mStartActivity);
mReuseTask = intentActivity.task;
- mReuseTask.performClearTaskLocked();
- mReuseTask.setIntent(mStartActivity);
// When we clear the task - focus will be adjusted, which will bring another task
// to top before we launch the activity we need. This will temporary swap their
// mTaskToReturnTo values and we don't want to overwrite them accidentally.
diff --git a/services/core/java/com/android/server/am/ConfigurationContainer.java b/services/core/java/com/android/server/am/ConfigurationContainer.java
index 30f5309..a3e95b8 100644
--- a/services/core/java/com/android/server/am/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/am/ConfigurationContainer.java
@@ -114,10 +114,14 @@
*/
void onParentChanged() {
final ConfigurationContainer parent = getParent();
- // Update full configuration of this container and all its children.
- onConfigurationChanged(parent != null ? parent.mFullConfiguration : Configuration.EMPTY);
- // Update merged override configuration of this container and all its children.
- onMergedOverrideConfigurationChanged();
+ // Removing parent usually means that we've detached this entity to destroy it or to attach
+ // to another parent. In both cases we don't need to update the configuration now.
+ if (parent != null) {
+ // Update full configuration of this container and all its children.
+ onConfigurationChanged(parent.mFullConfiguration);
+ // Update merged override configuration of this container and all its children.
+ onMergedOverrideConfigurationChanged();
+ }
}
abstract protected int getChildCount();
diff --git a/services/core/java/com/android/server/am/ProviderMap.java b/services/core/java/com/android/server/am/ProviderMap.java
index 878c0e7a..a6997f9 100644
--- a/services/core/java/com/android/server/am/ProviderMap.java
+++ b/services/core/java/com/android/server/am/ProviderMap.java
@@ -405,7 +405,7 @@
TransferPipe tp = new TransferPipe();
try {
r.proc.thread.dumpProvider(
- tp.getWriteFd().getFileDescriptor(), r.provider.asBinder(), args);
+ tp.getWriteFd(), r.provider.asBinder(), args);
tp.setBufferPrefix(" ");
// Short timeout, since blocking here can
// deadlock with the application.
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index f1ef947..f1d01e0 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -48,6 +48,10 @@
final IpConnectivityLog log = new IpConnectivityLog();
log.events = toProto(events);
log.droppedEvents = dropped;
+ if ((log.events.length > 0) || (dropped > 0)) {
+ // Only write version number if log has some information at all.
+ log.version = IpConnectivityMetrics.VERSION;
+ }
return IpConnectivityLog.toByteArray(log);
}
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index 2e3359f..be68173 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -19,19 +19,25 @@
import android.content.Context;
import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics;
+import android.net.metrics.ApfProgramEvent;
import android.net.metrics.IpConnectivityLog;
import android.os.IBinder;
import android.os.Parcelable;
+import android.provider.Settings;
import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.Base64;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.TokenBucket;
import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.function.ToIntFunction;
import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent;
@@ -40,10 +46,21 @@
private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
private static final boolean DBG = false;
+ // The logical version numbers of ipconnectivity.proto, corresponding to the
+ // "version" field of IpConnectivityLog.
+ private static final int NYC = 0;
+ private static final int NYC_MR1 = 1;
+ private static final int NYC_MR2 = 2;
+ public static final int VERSION = NYC_MR2;
+
private static final String SERVICE_NAME = IpConnectivityLog.SERVICE_NAME;
// Default size of the event buffer. Once the buffer is full, incoming events are dropped.
private static final int DEFAULT_BUFFER_SIZE = 2000;
+ // Maximum size of the event buffer.
+ private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10;
+
+ private static final int ERROR_RATE_LIMITED = -1;
// Lock ensuring that concurrent manipulations of the event buffer are correct.
// There are three concurrent operations to synchronize:
@@ -62,10 +79,19 @@
private int mDropped;
@GuardedBy("mLock")
private int mCapacity;
+ @GuardedBy("mLock")
+ private final ArrayMap<Class<?>, TokenBucket> mBuckets = makeRateLimitingBuckets();
+
+ private final ToIntFunction<Context> mCapacityGetter;
+
+ public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) {
+ super(ctx);
+ mCapacityGetter = capacityGetter;
+ initBuffer();
+ }
public IpConnectivityMetrics(Context ctx) {
- super(ctx);
- initBuffer();
+ this(ctx, READ_BUFFER_SIZE);
}
@Override
@@ -86,7 +112,7 @@
@VisibleForTesting
public int bufferCapacity() {
- return DEFAULT_BUFFER_SIZE; // TODO: read from config
+ return mCapacityGetter.applyAsInt(getContext());
}
private void initBuffer() {
@@ -104,6 +130,10 @@
if (event == null) {
return left;
}
+ if (isRateLimited(event)) {
+ // Do not count as a dropped event. TODO: consider adding separate counter
+ return ERROR_RATE_LIMITED;
+ }
if (left == 0) {
mDropped++;
return 0;
@@ -113,6 +143,11 @@
}
}
+ private boolean isRateLimited(ConnectivityMetricsEvent event) {
+ TokenBucket tb = mBuckets.get(event.data.getClass());
+ return (tb != null) && !tb.get();
+ }
+
private String flushEncodedOutput() {
final ArrayList<ConnectivityMetricsEvent> events;
final int dropped;
@@ -229,4 +264,20 @@
getContext().enforceCallingOrSelfPermission(what, "IpConnectivityMetrics");
}
};
+
+ private static final ToIntFunction<Context> READ_BUFFER_SIZE = (ctx) -> {
+ int size = Settings.Global.getInt(ctx.getContentResolver(),
+ Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
+ if (size <= 0) {
+ return DEFAULT_BUFFER_SIZE;
+ }
+ return Math.min(size, MAXIMUM_BUFFER_SIZE);
+ };
+
+ private static ArrayMap<Class<?>, TokenBucket> makeRateLimitingBuckets() {
+ ArrayMap<Class<?>, TokenBucket> map = new ArrayMap<>();
+ // one token every minute, 50 tokens max: burst of ~50 events every hour.
+ map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50));
+ return map;
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/MockableSystemProperties.java b/services/core/java/com/android/server/connectivity/MockableSystemProperties.java
new file mode 100644
index 0000000..4f68652
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/MockableSystemProperties.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 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.server.connectivity;
+
+import android.os.SystemProperties;
+
+public class MockableSystemProperties {
+ public boolean getBoolean(String key, boolean def) {
+ return SystemProperties.getBoolean(key, def);
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 6eb89fa..c73d1dd 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -211,7 +211,9 @@
private final NetworkRequest mDefaultRequest;
private final IpConnectivityLog mMetricsLog;
- private boolean mIsCaptivePortalCheckEnabled;
+ @VisibleForTesting
+ protected boolean mIsCaptivePortalCheckEnabled;
+
private boolean mUseHttps;
// Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
@@ -265,7 +267,8 @@
setInitialState(mDefaultState);
mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
+ Settings.Global.CAPTIVE_PORTAL_MODE, Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT)
+ != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE;
mUseHttps = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
@@ -632,7 +635,10 @@
@VisibleForTesting
protected CaptivePortalProbeResult isCaptivePortal() {
- if (!mIsCaptivePortalCheckEnabled) return new CaptivePortalProbeResult(204);
+ if (!mIsCaptivePortalCheckEnabled) {
+ validationLog("Validation disabled.");
+ return new CaptivePortalProbeResult(204);
+ }
URL pacUrl = null, httpsUrl = null, httpUrl = null, fallbackUrl = null;
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 921fd23..0beb227 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -52,7 +52,6 @@
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ResultReceiver;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
@@ -62,6 +61,7 @@
import android.util.Log;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.IndentingPrintWriter;
@@ -69,7 +69,6 @@
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import com.android.server.IoThread;
import com.android.server.connectivity.tethering.IControlsTethering;
import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
@@ -100,6 +99,8 @@
private final static boolean DBG = false;
private final static boolean VDBG = false;
+ protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning";
+
private static final Class[] messageClasses = {
Tethering.class, TetherMasterSM.class, TetherInterfaceStateMachine.class
};
@@ -127,6 +128,7 @@
private final INetworkStatsService mStatsService;
private final INetworkPolicyManager mPolicyManager;
private final Looper mLooper;
+ private final MockableSystemProperties mSystemProperties;
private static class TetherState {
public final TetherInterfaceStateMachine mStateMachine;
@@ -180,18 +182,19 @@
private boolean mWifiTetherRequested;
public Tethering(Context context, INetworkManagementService nmService,
- INetworkStatsService statsService, INetworkPolicyManager policyManager) {
+ INetworkStatsService statsService, INetworkPolicyManager policyManager,
+ Looper looper, MockableSystemProperties systemProperties) {
mContext = context;
mNMService = nmService;
mStatsService = statsService;
mPolicyManager = policyManager;
+ mLooper = looper;
+ mSystemProperties = systemProperties;
mPublicSync = new Object();
mTetherStates = new ArrayMap<>();
- // make our own thread so we don't anr the system
- mLooper = IoThread.get().getLooper();
mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper);
mTetherMasterSM.start();
@@ -394,10 +397,11 @@
*
* @return a boolean - {@code true} indicating tether provisioning is required by the carrier.
*/
- private boolean isTetherProvisioningRequired() {
+ @VisibleForTesting
+ protected boolean isTetherProvisioningRequired() {
String[] provisionApp = mContext.getResources().getStringArray(
com.android.internal.R.array.config_mobile_hotspot_provision_app);
- if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)
+ if (mSystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false)
|| provisionApp == null) {
return false;
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index df5def9..3b2dc34 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -284,7 +284,7 @@
+ mScreenBrightnessDarkConfig + ") to be less than or equal to "
+ "config_screenBrightnessDim (" + mScreenBrightnessDimConfig + ").");
}
- if (mScreenBrightnessDarkConfig > mScreenBrightnessDimConfig) {
+ if (mScreenBrightnessDarkConfig > screenBrightnessSettingMinimum) {
Slog.w(TAG, "Expected config_screenBrightnessDark ("
+ mScreenBrightnessDarkConfig + ") to be less than or equal to "
+ "config_screenBrightnessSettingMinimum ("
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
new file mode 100644
index 0000000..8ea4909
--- /dev/null
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2016 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.server.notification;
+
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * NotificationManagerService helper for auto-grouping notifications.
+ */
+public class GroupHelper {
+ private static final String TAG = "GroupHelper";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ protected static final int AUTOGROUP_AT_COUNT = 4;
+ protected static final String AUTOGROUP_KEY = "ranker_group";
+
+ private final Callback mCallback;
+
+ // Map of user : <Map of package : notification keys>. Only contains notifications that are not
+ // groupd by the app (aka no group or sort key).
+ Map<Integer, Map<String, LinkedHashSet<String>>> mUngroupedNotifications = new HashMap<>();
+
+ public GroupHelper(Callback callback) {;
+ mCallback = callback;
+ }
+
+ public void onNotificationPosted(StatusBarNotification sbn) {
+ if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey());
+ try {
+ List<String> notificationsToGroup = new ArrayList<>();
+ if (!sbn.isAppGroup()) {
+ // Not grouped by the app, add to the list of notifications for the app;
+ // send grouping update if app exceeds the autogrouping limit.
+ synchronized (mUngroupedNotifications) {
+ Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
+ = mUngroupedNotifications.get(sbn.getUserId());
+ if (ungroupedNotificationsByUser == null) {
+ ungroupedNotificationsByUser = new HashMap<>();
+ }
+ mUngroupedNotifications.put(sbn.getUserId(), ungroupedNotificationsByUser);
+ LinkedHashSet<String> notificationsForPackage
+ = ungroupedNotificationsByUser.get(sbn.getPackageName());
+ if (notificationsForPackage == null) {
+ notificationsForPackage = new LinkedHashSet<>();
+ }
+
+ notificationsForPackage.add(sbn.getKey());
+ ungroupedNotificationsByUser.put(sbn.getPackageName(), notificationsForPackage);
+
+ if (notificationsForPackage.size() >= AUTOGROUP_AT_COUNT) {
+ notificationsToGroup.addAll(notificationsForPackage);
+ }
+ }
+ if (notificationsToGroup.size() > 0) {
+ adjustAutogroupingSummary(sbn.getUserId(), sbn.getPackageName(),
+ notificationsToGroup.get(0), true);
+ adjustNotificationBundling(notificationsToGroup, true);
+ }
+ } else {
+ // Grouped, but not by us. Send updates to un-autogroup, if we grouped it.
+ maybeUngroup(sbn, false, sbn.getUserId());
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Failure processing new notification", e);
+ }
+ }
+
+ public void onNotificationRemoved(StatusBarNotification sbn) {
+ try {
+ maybeUngroup(sbn, true, sbn.getUserId());
+ } catch (Exception e) {
+ Slog.e(TAG, "Error processing canceled notification", e);
+ }
+ }
+
+ /**
+ * Un-autogroups notifications that are now grouped by the app. Additionally cancels
+ * autogrouping if the status change of this notification resulted in the loose notification
+ * count being under the limit.
+ */
+ private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) {
+ List<String> notificationsToUnAutogroup = new ArrayList<>();
+ boolean removeSummary = false;
+ synchronized (mUngroupedNotifications) {
+ Map<String, LinkedHashSet<String>> ungroupdNotificationsByUser
+ = mUngroupedNotifications.get(sbn.getUserId());
+ if (ungroupdNotificationsByUser == null || ungroupdNotificationsByUser.size() == 0) {
+ return;
+ }
+ LinkedHashSet<String> notificationsForPackage
+ = ungroupdNotificationsByUser.get(sbn.getPackageName());
+ if (notificationsForPackage == null || notificationsForPackage.size() == 0) {
+ return;
+ }
+ if (notificationsForPackage.remove(sbn.getKey())) {
+ if (!notificationGone) {
+ // Add the current notification to the ungrouping list if it still exists.
+ notificationsToUnAutogroup.add(sbn.getKey());
+ }
+ // If the status change of this notification has brought the number of loose
+ // notifications back below the limit, remove the summary and un-autogroup.
+ if (notificationsForPackage.size() == AUTOGROUP_AT_COUNT - 1) {
+ removeSummary = true;
+ for (String key : notificationsForPackage) {
+ notificationsToUnAutogroup.add(key);
+ }
+ }
+ }
+ }
+ if (notificationsToUnAutogroup.size() > 0) {
+ if (removeSummary) {
+ adjustAutogroupingSummary(userId, sbn.getPackageName(), null, false);
+ }
+ adjustNotificationBundling(notificationsToUnAutogroup, false);
+ }
+ }
+
+ private void adjustAutogroupingSummary(int userId, String packageName, String triggeringKey,
+ boolean summaryNeeded) {
+ if (summaryNeeded) {
+ mCallback.addAutoGroupSummary(userId, packageName, triggeringKey);
+ } else {
+ mCallback.removeAutoGroupSummary(userId, packageName);
+ }
+ }
+
+ private void adjustNotificationBundling(List<String> keys, boolean group) {
+ for (String key : keys) {
+ if (DEBUG) Log.i(TAG, "Sending grouping adjustment for: " + key + " group? " + group);
+ if (group) {
+ mCallback.addAutoGroup(key);
+ } else {
+ mCallback.removeAutoGroup(key);
+ }
+ }
+ }
+
+ protected interface Callback {
+ void addAutoGroup(String key);
+ void removeAutoGroup(String key);
+ void addAutoGroupSummary(int userId, String pkg, String triggeringKey);
+ void removeAutoGroupSummary(int user, String pkg);
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 61bf3bd..4e50567 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -311,6 +311,7 @@
private String mSystemNotificationSound;
private SnoozeHelper mSnoozeHelper;
+ private GroupHelper mGroupHelper;
private static class Archive {
final int mBufferSize;
@@ -1017,6 +1018,35 @@
}
}
}, mUserProfiles);
+ mGroupHelper = new GroupHelper(new GroupHelper.Callback() {
+ @Override
+ public void addAutoGroup(String key) {
+ synchronized (mNotificationList) {
+ addAutogroupKeyLocked(key);
+ }
+ mRankingHandler.requestSort();
+ }
+
+ @Override
+ public void removeAutoGroup(String key) {
+ synchronized (mNotificationList) {
+ removeAutogroupKeyLocked(key);
+ }
+ mRankingHandler.requestSort();
+ }
+
+ @Override
+ public void addAutoGroupSummary(int userId, String pkg, String triggeringKey) {
+ createAutoGroupSummary(userId, pkg, triggeringKey);
+ }
+
+ @Override
+ public void removeAutoGroupSummary(int userId, String pkg) {
+ synchronized (mNotificationList) {
+ clearAutogroupSummaryLocked(userId, pkg);
+ }
+ }
+ });
final File systemDir = new File(Environment.getDataDirectory(), "system");
mPolicyFile = new AtomicFile(new File(systemDir, "notification_policy.xml"));
@@ -2350,7 +2380,6 @@
mRankerServices.checkServiceTokenLocked(token);
applyAdjustmentLocked(adjustment);
}
- maybeAddAutobundleSummary(adjustment);
mRankingHandler.requestSort();
} finally {
Binder.restoreCallingIdentity(identity);
@@ -2369,9 +2398,6 @@
applyAdjustmentLocked(adjustment);
}
}
- for (Adjustment adjustment : adjustments) {
- maybeAddAutobundleSummary(adjustment);
- }
mRankingHandler.requestSort();
} finally {
Binder.restoreCallingIdentity(identity);
@@ -2380,7 +2406,6 @@
};
private void applyAdjustmentLocked(Adjustment adjustment) {
- maybeClearAutobundleSummaryLocked(adjustment);
NotificationRecord n = mNotificationsByKey.get(adjustment.getKey());
if (n == null) {
return;
@@ -2390,107 +2415,97 @@
}
if (adjustment.getSignals() != null) {
Bundle.setDefusable(adjustment.getSignals(), true);
- final String autoGroupKey = adjustment.getSignals().getString(
- Adjustment.GROUP_KEY_OVERRIDE_KEY, null);
- if (autoGroupKey == null) {
- EventLogTags.writeNotificationUnautogrouped(adjustment.getKey());
- } else {
- EventLogTags.writeNotificationAutogrouped(adjustment.getKey());
- }
- n.sbn.setOverrideGroupKey(autoGroupKey);
+ // TODO: apply signals
}
}
- // Clears the 'fake' auto-bunding summary.
- private void maybeClearAutobundleSummaryLocked(Adjustment adjustment) {
- if (adjustment.getSignals() != null) {
- Bundle.setDefusable(adjustment.getSignals(), true);
- if (adjustment.getSignals().containsKey(Adjustment.NEEDS_AUTOGROUPING_KEY)
- && !adjustment.getSignals().getBoolean(Adjustment.NEEDS_AUTOGROUPING_KEY, false)) {
- ArrayMap<String, String> summaries =
- mAutobundledSummaries.get(adjustment.getUser());
- if (summaries != null && summaries.containsKey(adjustment.getPackage())) {
- // Clear summary.
- final NotificationRecord removed = mNotificationsByKey.get(
- summaries.remove(adjustment.getPackage()));
- if (removed != null) {
- mNotificationList.remove(removed);
- cancelNotificationLocked(removed, false, REASON_UNAUTOBUNDLED);
- }
- }
+ private void addAutogroupKeyLocked(String key) {
+ NotificationRecord n = mNotificationsByKey.get(key);
+ if (n == null) {
+ return;
+ }
+ n.sbn.setOverrideGroupKey(GroupHelper.AUTOGROUP_KEY);
+ EventLogTags.writeNotificationAutogrouped(key);
+ }
+
+ private void removeAutogroupKeyLocked(String key) {
+ NotificationRecord n = mNotificationsByKey.get(key);
+ if (n == null) {
+ return;
+ }
+ n.sbn.setOverrideGroupKey(null);
+ EventLogTags.writeNotificationUnautogrouped(key);
+ }
+
+ // Clears the 'fake' auto-group summary.
+ private void clearAutogroupSummaryLocked(int userId, String pkg) {
+ ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
+ if (summaries != null && summaries.containsKey(pkg)) {
+ // Clear summary.
+ final NotificationRecord removed = mNotificationsByKey.get(summaries.remove(pkg));
+ if (removed != null) {
+ mNotificationList.remove(removed);
+ cancelNotificationLocked(removed, false, REASON_UNAUTOBUNDLED);
}
}
}
// Posts a 'fake' summary for a package that has exceeded the solo-notification limit.
- private void maybeAddAutobundleSummary(Adjustment adjustment) {
- if (adjustment.getSignals() != null) {
- Bundle.setDefusable(adjustment.getSignals(), true);
- if (adjustment.getSignals().getBoolean(Adjustment.NEEDS_AUTOGROUPING_KEY, false)) {
- final String newAutoBundleKey =
- adjustment.getSignals().getString(Adjustment.GROUP_KEY_OVERRIDE_KEY, null);
- int userId = -1;
- NotificationRecord summaryRecord = null;
- synchronized (mNotificationList) {
- NotificationRecord notificationRecord =
- mNotificationsByKey.get(adjustment.getKey());
- if (notificationRecord == null) {
- // The notification could have been cancelled again already. A successive
- // adjustment will post a summary if needed.
- return;
- }
- final StatusBarNotification adjustedSbn = notificationRecord.sbn;
- userId = adjustedSbn.getUser().getIdentifier();
- ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
- if (summaries == null) {
- summaries = new ArrayMap<>();
- }
- mAutobundledSummaries.put(userId, summaries);
- if (!summaries.containsKey(adjustment.getPackage())
- && newAutoBundleKey != null) {
- // Add summary
- final ApplicationInfo appInfo =
- adjustedSbn.getNotification().extras.getParcelable(
- Notification.EXTRA_BUILDER_APPLICATION_INFO);
- final Bundle extras = new Bundle();
- extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
- final Notification summaryNotification =
- new Notification.Builder(getContext()).setSmallIcon(
- adjustedSbn.getNotification().getSmallIcon())
- .setGroupSummary(true)
- .setGroup(newAutoBundleKey)
- .setFlag(Notification.FLAG_AUTOGROUP_SUMMARY, true)
- .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
- .setColor(adjustedSbn.getNotification().color)
- .setLocalOnly(true)
- .build();
- summaryNotification.extras.putAll(extras);
- Intent appIntent = getContext().getPackageManager()
- .getLaunchIntentForPackage(adjustment.getPackage());
- if (appIntent != null) {
- summaryNotification.contentIntent = PendingIntent.getActivityAsUser(
- getContext(), 0, appIntent, 0, null,
- UserHandle.of(userId));
- }
- final StatusBarNotification summarySbn =
- new StatusBarNotification(adjustedSbn.getPackageName(),
- adjustedSbn.getOpPkg(),
- Integer.MAX_VALUE, Adjustment.GROUP_KEY_OVERRIDE_KEY,
- adjustedSbn.getUid(), adjustedSbn.getInitialPid(),
- summaryNotification, adjustedSbn.getUser(),
- newAutoBundleKey,
- System.currentTimeMillis());
- summaryRecord = new NotificationRecord(getContext(), summarySbn,
- mRankingHelper.getNotificationChannel(adjustedSbn.getPackageName(),
- adjustedSbn.getUid(),
- adjustedSbn.getNotification().getNotificationChannel()));
- summaries.put(adjustment.getPackage(), summarySbn.getKey());
- }
- }
- if (summaryRecord != null) {
- mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord));
- }
+ private void createAutoGroupSummary(int userId, String pkg, String triggeringKey) {
+ NotificationRecord summaryRecord = null;
+ synchronized (mNotificationList) {
+ NotificationRecord notificationRecord = mNotificationsByKey.get(triggeringKey);
+ if (notificationRecord == null) {
+ // The notification could have been cancelled again already. A successive
+ // adjustment will post a summary if needed.
+ return;
}
+ final StatusBarNotification adjustedSbn = notificationRecord.sbn;
+ userId = adjustedSbn.getUser().getIdentifier();
+ ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
+ if (summaries == null) {
+ summaries = new ArrayMap<>();
+ }
+ mAutobundledSummaries.put(userId, summaries);
+ if (!summaries.containsKey(pkg)) {
+ // Add summary
+ final ApplicationInfo appInfo =
+ adjustedSbn.getNotification().extras.getParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO);
+ final Bundle extras = new Bundle();
+ extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
+ final Notification summaryNotification =
+ new Notification.Builder(getContext()).setSmallIcon(
+ adjustedSbn.getNotification().getSmallIcon())
+ .setGroupSummary(true)
+ .setGroup(GroupHelper.AUTOGROUP_KEY)
+ .setFlag(Notification.FLAG_AUTOGROUP_SUMMARY, true)
+ .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
+ .setColor(adjustedSbn.getNotification().color)
+ .setLocalOnly(true)
+ .build();
+ summaryNotification.extras.putAll(extras);
+ Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg);
+ if (appIntent != null) {
+ summaryNotification.contentIntent = PendingIntent.getActivityAsUser(
+ getContext(), 0, appIntent, 0, null, UserHandle.of(userId));
+ }
+ final StatusBarNotification summarySbn =
+ new StatusBarNotification(adjustedSbn.getPackageName(),
+ adjustedSbn.getOpPkg(), Integer.MAX_VALUE,
+ GroupHelper.AUTOGROUP_KEY, adjustedSbn.getUid(),
+ adjustedSbn.getInitialPid(), summaryNotification,
+ adjustedSbn.getUser(), GroupHelper.AUTOGROUP_KEY,
+ System.currentTimeMillis());
+ summaryRecord = new NotificationRecord(getContext(), summarySbn,
+ mRankingHelper.getNotificationChannel(adjustedSbn.getPackageName(),
+ adjustedSbn.getUid(),
+ adjustedSbn.getNotification().getNotificationChannel()));
+ summaries.put(pkg, summarySbn.getKey());
+ }
+ }
+ if (summaryRecord != null) {
+ mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord));
}
}
@@ -2690,6 +2705,12 @@
(r.mOriginalFlags & ~Notification.FLAG_FOREGROUND_SERVICE);
mRankingHelper.sort(mNotificationList);
mListeners.notifyPostedLocked(sbn, sbn /* oldSbn */);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mGroupHelper.onNotificationPosted(sbn);
+ }
+ });
}
}
};
@@ -2914,10 +2935,22 @@
if (notification.getSmallIcon() != null) {
StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
mListeners.notifyPostedLocked(n, oldSbn);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mGroupHelper.onNotificationPosted(n);
+ }
+ });
} else {
Slog.e(TAG, "Not posting notification without small icon: " + notification);
if (old != null && !old.isCanceled) {
mListeners.notifyRemovedLocked(n);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mGroupHelper.onNotificationRemoved(n);
+ }
+ });
}
// ATTENTION: in a future release we will bail out here
// so that we do not play sounds, show lights, etc. for invalid
@@ -3525,6 +3558,12 @@
if (r.getNotification().getSmallIcon() != null) {
r.isCanceled = true;
mListeners.notifyRemovedLocked(r.sbn);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mGroupHelper.onNotificationRemoved(r.sbn);
+ }
+ });
}
final String canceledKey = r.getKey();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e48d6fb..974d95d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -466,11 +466,11 @@
private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
/**
- * If VENDOR_OVERLAY_SKU_PROPERTY is set, search for runtime resource overlay APKs also in
- * VENDOR_OVERLAY_DIR/<value of VENDOR_OVERLAY_SKU_PROPERTY> in addition to
+ * If VENDOR_OVERLAY_THEME_PROPERTY is set, search for runtime resource overlay APKs also in
+ * VENDOR_OVERLAY_DIR/<value of VENDOR_OVERLAY_THEME_PROPERTY> in addition to
* VENDOR_OVERLAY_DIR.
*/
- private static final String VENDOR_OVERLAY_SKU_PROPERTY = "ro.boot.vendor.overlay.sku";
+ private static final String VENDOR_OVERLAY_THEME_PROPERTY = "ro.boot.vendor.overlay.theme";
private static int DEFAULT_EPHEMERAL_HASH_PREFIX_MASK = 0xFFFFF000;
private static int DEFAULT_EPHEMERAL_HASH_PREFIX_COUNT = 5;
@@ -2288,9 +2288,9 @@
// Collect vendor overlay packages. (Do this before scanning any apps.)
// For security and version matching reason, only consider
// overlay packages if they reside in the right directory.
- String overlaySkuDir = SystemProperties.get(VENDOR_OVERLAY_SKU_PROPERTY);
- if (!overlaySkuDir.isEmpty()) {
- scanDirTracedLI(new File(VENDOR_OVERLAY_DIR, overlaySkuDir), mDefParseFlags
+ String overlayThemeDir = SystemProperties.get(VENDOR_OVERLAY_THEME_PROPERTY);
+ if (!overlayThemeDir.isEmpty()) {
+ scanDirTracedLI(new File(VENDOR_OVERLAY_DIR, overlayThemeDir), mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index bb76449..9f0f11a 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -36,6 +36,7 @@
import android.util.Log;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
+import android.webkit.WebViewZygote;
import com.android.internal.util.XmlUtils;
@@ -268,6 +269,11 @@
return pm.getPackageInfo(configInfo.packageName, PACKAGE_FLAGS);
}
+ @Override
+ public void setMultiprocessEnabled(boolean enabled) {
+ WebViewZygote.setMultiprocessEnabled(enabled);
+ }
+
// flags declaring we want extra info from the package manager for webview providers
private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA
| PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
index 7bde37a..7c934fc 100644
--- a/services/core/java/com/android/server/webkit/SystemInterface.java
+++ b/services/core/java/com/android/server/webkit/SystemInterface.java
@@ -48,4 +48,6 @@
public boolean systemIsDebuggable();
public PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
throws NameNotFoundException;
+
+ public void setMultiprocessEnabled(boolean enabled);
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index b69a8c1..57cd768 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -20,7 +20,12 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Base64;
import android.util.Slog;
import android.webkit.WebViewFactory;
@@ -73,6 +78,7 @@
private SystemInterface mSystemInterface;
private WebViewUpdater mWebViewUpdater;
+ private SettingsObserver mSettingsObserver;
private Context mContext;
public WebViewUpdateServiceImpl(Context context, SystemInterface systemInterface) {
@@ -92,6 +98,10 @@
void prepareWebViewInSystemServer() {
updateFallbackStateOnBoot();
mWebViewUpdater.prepareWebViewInSystemServer();
+
+ // Register for changes in the multiprocess developer option. This has to be done
+ // here, since the update service gets created before the ContentResolver service.
+ mSettingsObserver = new SettingsObserver();
}
private boolean existsValidNonFallbackProvider(WebViewProviderInfo[] providers) {
@@ -709,4 +719,41 @@
& ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0));
}
+ /**
+ * Watches for changes in the WEBVIEW_MULTIPROCESS setting and lets
+ * the WebViewZygote know, so it can start or stop the zygote process
+ * appropriately.
+ */
+ private class SettingsObserver extends ContentObserver {
+ private final ContentResolver mResolver;
+
+ SettingsObserver() {
+ super(new Handler());
+
+ mResolver = mContext.getContentResolver();
+ mResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.WEBVIEW_MULTIPROCESS),
+ false, this);
+
+ // Push the current value of the setting immediately.
+ notifyZygote();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ notifyZygote();
+ }
+
+ private void notifyZygote() {
+ boolean enableMultiprocess = false;
+
+ try {
+ enableMultiprocess = Settings.Global.getInt(mResolver,
+ Settings.Global.WEBVIEW_MULTIPROCESS) == 1;
+ } catch (Settings.SettingNotFoundException ex) {
+ }
+
+ mSystemInterface.setMultiprocessEnabled(enableMultiprocess);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 429cb2d..e774259 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -31,7 +31,6 @@
import android.view.Choreographer;
import android.view.Display;
import android.view.SurfaceControl;
-import android.view.WindowManagerPolicy;
import android.view.animation.Animation;
import android.view.animation.Transformation;
@@ -394,7 +393,7 @@
}
if (mService.mInputMethodTarget != null
&& mService.mInputMethodTarget.mAppToken == mAppToken) {
- mService.moveInputMethodWindowsIfNeededLocked(true);
+ mAppToken.getDisplayContent().moveInputMethodWindowsIfNeeded(true);
}
if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + mAppToken
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index ee7c6d2..d46b535 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -425,8 +425,7 @@
}
if (destroyedSomething) {
final DisplayContent dc = getDisplayContent();
- mService.mLayersController.assignLayersLocked(dc.getWindowList());
- dc.setLayoutNeeded();
+ dc.assignWindowLayers(true /*setLayoutNeeded*/);
}
}
@@ -950,7 +949,7 @@
mService.updateFocusedWindowLocked(
UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
- mService.getDefaultDisplayContentLocked().setLayoutNeeded();
+ getDisplayContent().setLayoutNeeded();
mService.mWindowPlacerLocked.performSurfacePlacement();
Binder.restoreCallingIdentity(origId);
return true;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3681123..7cd9971 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -32,27 +32,61 @@
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_TOP;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE;
+import static android.view.WindowManager.LayoutParams.NEEDS_MENU_UNSET;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
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_BOOT_PROGRESS;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
+import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
+import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+import static com.android.server.wm.WindowAnimator.KEYGUARD_ANIMATING_OUT;
+import static com.android.server.wm.WindowAnimator.KEYGUARD_ANIM_TIMEOUT_MS;
+import static com.android.server.wm.WindowAnimator.KEYGUARD_NOT_SHOWN;
+import static com.android.server.wm.WindowAnimator.KEYGUARD_SHOWN;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEYGUARD;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT;
+import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
import static com.android.server.wm.WindowManagerService.dipToPixel;
import static com.android.server.wm.WindowManagerService.localLOGV;
import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
+import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
+import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
+import static com.android.server.wm.WindowSurfacePlacer.SET_FORCE_HIDING_CHANGED;
+import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE;
import android.annotation.NonNull;
import android.app.ActivityManager.StackId;
@@ -66,13 +100,20 @@
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Slog;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.IWindow;
+import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
import com.android.internal.util.FastPrintWriter;
+import com.android.internal.view.IInputMethodClient;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -92,6 +133,7 @@
* WindowManagerService.mWindowMap.
*/
class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer> {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayContent" : TAG_WM;
/** Unique identifier of this stack. */
private final int mDisplayId;
@@ -171,13 +213,24 @@
// the display's direct children should be allowed.
private boolean mRemovingDisplay = false;
+ private final WindowLayersController mLayersController;
+ final WallpaperController mWallpaperController;
+ int mInputMethodAnimLayerAdjustment;
+
/**
* @param display May not be null.
* @param service You know.
+ * @param layersController window layer controller used to assign layer to the windows on this
+ * display.
+ * @param wallpaperController wallpaper windows controller used to adjust the positioning of the
+ * wallpaper windows in the window list.
*/
- DisplayContent(Display display, WindowManagerService service) {
+ DisplayContent(Display display, WindowManagerService service,
+ WindowLayersController layersController, WallpaperController wallpaperController) {
mDisplay = display;
mDisplayId = display.getDisplayId();
+ mLayersController = layersController;
+ mWallpaperController = wallpaperController;
display.getDisplayInfo(mDisplayInfo);
display.getMetrics(mDisplayMetrics);
isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
@@ -325,33 +378,79 @@
@Override
int getOrientation() {
- if (mService.isStackVisibleLocked(DOCKED_STACK_ID)
- || mService.isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) {
- // Apps and their containers are not allowed to specify an orientation while the docked
- // or freeform stack is visible...except for the home stack/task if the docked stack is
- // minimized and it actually set something.
- if (mHomeStack != null && mHomeStack.isVisible()
- && mDividerControllerLocked.isMinimizedDock()) {
- final int orientation = mHomeStack.getOrientation();
- if (orientation != SCREEN_ORIENTATION_UNSET) {
- return orientation;
- }
+ final WindowManagerPolicy policy = mService.mPolicy;
+
+ // TODO: All the logic before the last return statement in this method should really go in
+ // #NonAppWindowContainer.getOrientation() since it is trying to decide orientation based
+ // on non-app windows. But, we can not do that until the window list is always correct in
+ // terms of z-ordering based on layers.
+ if (mService.mDisplayFrozen) {
+ if (mService.mLastWindowForcedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
+ "Display is frozen, return " + mService.mLastWindowForcedOrientation);
+ // If the display is frozen, some activities may be in the middle of restarting, and
+ // thus have removed their old window. If the window has the flag to hide the lock
+ // screen, then the lock screen can re-appear and inflict its own orientation on us.
+ // Keep the orientation stable until this all settles down.
+ return mService.mLastWindowForcedOrientation;
+ } else if (policy.isKeyguardLocked()) {
+ // Use the last orientation the while the display is frozen with the keyguard
+ // locked. This could be the keyguard forced orientation or from a SHOW_WHEN_LOCKED
+ // window. We don't want to check the show when locked window directly though as
+ // things aren't stable while the display is frozen, for example the window could be
+ // momentarily unavailable due to activity relaunch.
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display is frozen while keyguard locked, "
+ + "return " + mService.mLastOrientation);
+ return mService.mLastOrientation;
}
- return SCREEN_ORIENTATION_UNSPECIFIED;
+ } else {
+ for (int pos = mWindows.size() - 1; pos >= 0; --pos) {
+ final WindowState win = mWindows.get(pos);
+ if (win.mAppToken != null) {
+ // We hit an application window. so the orientation will be determined by the
+ // app window. No point in continuing further.
+ break;
+ }
+ if (!win.isVisibleLw() || !win.mPolicyVisibilityAfterAnim) {
+ continue;
+ }
+ int req = win.mAttrs.screenOrientation;
+ if(req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND) {
+ continue;
+ }
+
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM, win + " forcing orientation to " + req);
+ if (policy.isKeyguardHostWindow(win.mAttrs)) {
+ mService.mLastKeyguardForcedOrientation = req;
+ }
+ return (mService.mLastWindowForcedOrientation = req);
+ }
+ mService.mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+ if (policy.isKeyguardLocked()) {
+ // The screen is locked and no top system window is requesting an orientation.
+ // Return either the orientation of the show-when-locked app (if there is any) or
+ // the orientation of the keyguard. No point in searching from the rest of apps.
+ WindowState winShowWhenLocked = (WindowState) policy.getWinShowWhenLockedLw();
+ AppWindowToken appShowWhenLocked = winShowWhenLocked == null
+ ? null : winShowWhenLocked.mAppToken;
+ if (appShowWhenLocked != null) {
+ int req = appShowWhenLocked.getOrientation();
+ if (req == SCREEN_ORIENTATION_BEHIND) {
+ req = mService.mLastKeyguardForcedOrientation;
+ }
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Done at " + appShowWhenLocked
+ + " -- show when locked, return " + req);
+ return req;
+ }
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
+ "No one is requesting an orientation when the screen is locked");
+ return mService.mLastKeyguardForcedOrientation;
+ }
}
- final int orientation = super.getOrientation();
- if (orientation != SCREEN_ORIENTATION_UNSET && orientation != SCREEN_ORIENTATION_BEHIND) {
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
- "App is requesting an orientation, return " + orientation);
- return orientation;
- }
-
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
- "No app is requesting an orientation, return " + mService.mLastOrientation);
- // The next app has not been requested to be visible, so we keep the current orientation
- // to prevent freezing/unfreezing the display too early.
- return mService.mLastOrientation;
+ // Top system windows are not requesting an orientation. Start searching from apps.
+ return mTaskStackContainers.getOrientation();
}
void updateDisplayInfo() {
@@ -691,6 +790,39 @@
}
}
+ void setInputMethodAnimLayerAdjustment(int adj) {
+ if (DEBUG_LAYERS) Slog.v(TAG_WM, "Setting im layer adj to " + adj);
+ mInputMethodAnimLayerAdjustment = adj;
+ final WindowState imw = mService.mInputMethodWindow;
+ if (imw != null) {
+ imw.adjustAnimLayer(adj);
+ }
+ for (int i = mService.mInputMethodDialogs.size() - 1; i >= 0; i--) {
+ final WindowState dialog = mService.mInputMethodDialogs.get(i);
+ // TODO: This and other places setting mAnimLayer can probably use WS.adjustAnimLayer,
+ // but need to make sure we are not setting things twice for child windows that are
+ // already in the list.
+ dialog.mWinAnimator.mAnimLayer = dialog.mLayer + adj;
+ if (DEBUG_LAYERS) Slog.v(TAG_WM, "IM win " + imw
+ + " anim layer: " + dialog.mWinAnimator.mAnimLayer);
+ }
+ }
+
+ /**
+ * If a window that has an animation specifying a colored background and the current wallpaper
+ * is visible, then the color goes *below* the wallpaper so we don't cause the wallpaper to
+ * suddenly disappear.
+ */
+ int getLayerForAnimationBackground(WindowStateAnimator winAnimator) {
+ for (int i = mWindows.size() - 1; i >= 0; --i) {
+ final WindowState win = mWindows.get(i);
+ if (win.mIsWallpaper && win.isVisibleNow()) {
+ return win.mWinAnimator.mAnimLayer;
+ }
+ }
+ return winAnimator.mAnimLayer;
+ }
+
void prepareFreezingTaskBounds() {
for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
final TaskStack stack = mTaskStackContainers.get(stackNdx);
@@ -798,6 +930,11 @@
mDimLayerController.dump(prefix + " ", pw);
pw.println();
mDividerControllerLocked.dump(prefix + " ", pw);
+
+ if (mInputMethodAnimLayerAdjustment != 0) {
+ pw.println(subPrefix
+ + "mInputMethodAnimLayerAdjustment=" + mInputMethodAnimLayerAdjustment);
+ }
}
@Override
@@ -1071,6 +1208,36 @@
mWindows.add(index, win);
}
+ boolean removeFromWindowList(WindowState win) {
+ return mWindows.remove(win);
+ }
+
+ private int removeWindowAndChildrenFromWindowList(WindowState win, int interestingPos) {
+ final WindowList windows = getWindowList();
+ int wpos = windows.indexOf(win);
+ if (wpos < 0) {
+ return interestingPos;
+ }
+
+ if (wpos < interestingPos) interestingPos--;
+ if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Temp removing at " + wpos + ": " + this);
+ windows.remove(wpos);
+ mService.mWindowsChanged = true;
+ int childWinCount = win.mChildren.size();
+ while (childWinCount > 0) {
+ childWinCount--;
+ final WindowState cw = win.mChildren.get(childWinCount);
+ int cpos = windows.indexOf(cw);
+ if (cpos >= 0) {
+ if (cpos < interestingPos) interestingPos--;
+ if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM,
+ "Temp removing child at " + cpos + ": " + cw);
+ windows.remove(cpos);
+ }
+ }
+ return interestingPos;
+ }
+
void addChildWindowToWindowList(WindowState win) {
final WindowState parentWindow = win.getParentWindow();
@@ -1113,6 +1280,20 @@
}
}
+ /** Updates the layer assignment of windows on this display. */
+ void assignWindowLayers(boolean setLayoutNeeded) {
+ mLayersController.assignWindowLayers(mWindows);
+ if (setLayoutNeeded) {
+ setLayoutNeeded();
+ }
+ }
+
+ void adjustWallpaperWindows() {
+ if (mWallpaperController.adjustWallpaperWindows(mWindows)) {
+ assignWindowLayers(true /*setLayoutNeeded*/);
+ }
+ }
+
/**
* Z-orders the display window list so that:
* <ul>
@@ -1218,6 +1399,371 @@
return windowList;
}
+ private void reAddToWindowList(WindowState win) {
+ win.mToken.addWindow(win);
+ // This is a hack to get all of the child windows added as well at the right position. Child
+ // windows should be rare and this case should be rare, so it shouldn't be that big a deal.
+ int wpos = mWindows.indexOf(win);
+ if (wpos >= 0) {
+ if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "ReAdd removing from " + wpos + ": " + win);
+ mWindows.remove(wpos);
+ mService.mWindowsChanged = true;
+ win.reAddWindow(wpos);
+ }
+ }
+
+ void moveInputMethodDialogs(int pos) {
+ ArrayList<WindowState> dialogs = mService.mInputMethodDialogs;
+
+ final int N = dialogs.size();
+ if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Removing " + N + " dialogs w/pos=" + pos);
+ for (int i = 0; i < N; i++) {
+ pos = removeWindowAndChildrenFromWindowList(dialogs.get(i), pos);
+ }
+ if (DEBUG_INPUT_METHOD) {
+ Slog.v(TAG_WM, "Window list w/pos=" + pos);
+ logWindowList(mWindows, " ");
+ }
+
+ WindowState ime = mService.mInputMethodWindow;
+ if (pos >= 0) {
+ // Skip windows owned by the input method.
+ if (ime != null) {
+ while (pos < mWindows.size()) {
+ WindowState wp = mWindows.get(pos);
+ if (wp == ime || wp.getParentWindow() == ime) {
+ pos++;
+ continue;
+ }
+ break;
+ }
+ }
+ if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Adding " + N + " dialogs at pos=" + pos);
+ for (int i=0; i<N; i++) {
+ WindowState win = dialogs.get(i);
+ pos = win.reAddWindow(pos);
+ }
+ if (DEBUG_INPUT_METHOD) {
+ Slog.v(TAG_WM, "Final window list:");
+ logWindowList(mWindows, " ");
+ }
+ return;
+ }
+ for (int i=0; i<N; i++) {
+ WindowState win = dialogs.get(i);
+ reAddToWindowList(win);
+ if (DEBUG_INPUT_METHOD) {
+ Slog.v(TAG_WM, "No IM target, final list:");
+ logWindowList(mWindows, " ");
+ }
+ }
+ }
+
+ boolean moveInputMethodWindowsIfNeeded(boolean needAssignLayers) {
+ final WindowState imWin = mService.mInputMethodWindow;
+ final int DN = mService.mInputMethodDialogs.size();
+ if (imWin == null && DN == 0) {
+ return false;
+ }
+
+ // TODO(multidisplay): IMEs are only supported on the default display.
+ WindowList windows = mWindows;
+
+ int imPos = findDesiredInputMethodWindowIndex(true);
+ if (imPos >= 0) {
+ // In this case, the input method windows are to be placed
+ // immediately above the window they are targeting.
+
+ // First check to see if the input method windows are already
+ // located here, and contiguous.
+ final int N = windows.size();
+ final WindowState firstImWin = imPos < N ? windows.get(imPos) : null;
+
+ // Figure out the actual input method window that should be
+ // at the bottom of their stack.
+ WindowState baseImWin = imWin != null ? imWin : mService.mInputMethodDialogs.get(0);
+ final WindowState cw = baseImWin.getBottomChild();
+ if (cw != null && cw.mSubLayer < 0) {
+ baseImWin = cw;
+ }
+
+ if (firstImWin == baseImWin) {
+ // The windows haven't moved... but are they still contiguous?
+ // First find the top IM window.
+ int pos = imPos+1;
+ while (pos < N) {
+ if (!(windows.get(pos)).mIsImWindow) {
+ break;
+ }
+ pos++;
+ }
+ pos++;
+ // Now there should be no more input method windows above.
+ while (pos < N) {
+ if ((windows.get(pos)).mIsImWindow) {
+ break;
+ }
+ pos++;
+ }
+ if (pos >= N) {
+ return false;
+ }
+ }
+
+ if (imWin != null) {
+ if (DEBUG_INPUT_METHOD) {
+ Slog.v(TAG_WM, "Moving IM from " + imPos);
+ logWindowList(windows, " ");
+ }
+ imPos = removeWindowAndChildrenFromWindowList(imWin, imPos);
+ if (DEBUG_INPUT_METHOD) {
+ Slog.v(TAG_WM, "List after removing with new pos " + imPos + ":");
+ logWindowList(windows, " ");
+ }
+ imWin.reAddWindow(imPos);
+ if (DEBUG_INPUT_METHOD) {
+ Slog.v(TAG_WM, "List after moving IM to " + imPos + ":");
+ logWindowList(windows, " ");
+ }
+ if (DN > 0) moveInputMethodDialogs(imPos+1);
+ } else {
+ moveInputMethodDialogs(imPos);
+ }
+
+ } else {
+ // In this case, the input method windows go in a fixed layer,
+ // because they aren't currently associated with a focus window.
+
+ if (imWin != null) {
+ if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Moving IM from " + imPos);
+ removeWindowAndChildrenFromWindowList(imWin, 0);
+ reAddToWindowList(imWin);
+ if (DEBUG_INPUT_METHOD) {
+ Slog.v(TAG_WM, "List with no IM target:");
+ logWindowList(windows, " ");
+ }
+ if (DN > 0) moveInputMethodDialogs(-1);
+ } else {
+ moveInputMethodDialogs(-1);
+ }
+
+ }
+
+ if (needAssignLayers) {
+ assignWindowLayers(false /* setLayoutNeeded */);
+ }
+
+ return true;
+ }
+
+ /**
+ * Dig through the WindowStates and find the one that the Input Method will target.
+ * @param willMove
+ * @return The index+1 in mWindows of the discovered target.
+ */
+ int findDesiredInputMethodWindowIndex(boolean willMove) {
+ // TODO(multidisplay): Needs some serious rethought when the target and IME are not on the
+ // same display. Or even when the current IME/target are not on the same screen as the next
+ // IME/target. For now only look for input windows on the main screen.
+ final WindowList windows = getWindowList();
+ WindowState w = null;
+ int i;
+ for (i = windows.size() - 1; i >= 0; --i) {
+ WindowState win = windows.get(i);
+
+ if (DEBUG_INPUT_METHOD && willMove) Slog.i(TAG_WM, "Checking window @" + i
+ + " " + win + " fl=0x" + Integer.toHexString(win.mAttrs.flags));
+ if (canBeImeTarget(win)) {
+ w = win;
+ //Slog.i(TAG_WM, "Putting input method here!");
+
+ // Yet more tricksyness! If this window is a "starting" window, we do actually want
+ // to be on top of it, but it is not -really- where input will go. So if the caller
+ // is not actually looking to move the IME, look down below for a real window to
+ // target...
+ if (!willMove && w.mAttrs.type == TYPE_APPLICATION_STARTING && i > 0) {
+ WindowState wb = windows.get(i-1);
+ if (wb.mAppToken == w.mAppToken && canBeImeTarget(wb)) {
+ i--;
+ w = wb;
+ }
+ }
+ break;
+ }
+ }
+
+ // Now w is either mWindows[0] or an IME (or null if mWindows is empty).
+
+ if (DEBUG_INPUT_METHOD && willMove) Slog.v(TAG_WM, "Proposed new IME target: " + w);
+
+ // Now, a special case -- if the last target's window is in the process of exiting, and is
+ // above the new target, keep on the last target to avoid flicker. Consider for example a
+ // Dialog with the IME shown: when the Dialog is dismissed, we want to keep the IME above it
+ // until it is completely gone so it doesn't drop behind the dialog or its full-screen
+ // scrim.
+ final WindowState curTarget = mService.mInputMethodTarget;
+ if (curTarget != null
+ && curTarget.isDisplayedLw()
+ && curTarget.isClosing()
+ && (w == null || curTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer)) {
+ if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Current target higher, not changing");
+ return windows.indexOf(curTarget) + 1;
+ }
+
+ if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Desired input method target="
+ + w + " willMove=" + willMove);
+
+ if (willMove && w != null) {
+ AppWindowToken token = curTarget == null ? null : curTarget.mAppToken;
+ if (token != null) {
+
+ // Now some fun for dealing with window animations that modify the Z order. We need
+ // to look at all windows below the current target that are in this app, finding the
+ // highest visible one in layering.
+ WindowState highestTarget = null;
+ int highestPos = 0;
+ if (token.mAppAnimator.animating || token.mAppAnimator.animation != null) {
+ WindowList curWindows = token.getDisplayContent().getWindowList();
+ int pos = curWindows.indexOf(curTarget);
+ while (pos >= 0) {
+ WindowState win = curWindows.get(pos);
+ if (win.mAppToken != token) {
+ break;
+ }
+ if (!win.mRemoved) {
+ if (highestTarget == null || win.mWinAnimator.mAnimLayer >
+ highestTarget.mWinAnimator.mAnimLayer) {
+ highestTarget = win;
+ highestPos = pos;
+ }
+ }
+ pos--;
+ }
+ }
+
+ if (highestTarget != null) {
+ final AppTransition appTransition = mService.mAppTransition;
+ if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, appTransition + " " + highestTarget
+ + " animating=" + highestTarget.mWinAnimator.isAnimationSet()
+ + " layer=" + highestTarget.mWinAnimator.mAnimLayer
+ + " new layer=" + w.mWinAnimator.mAnimLayer);
+
+ if (appTransition.isTransitionSet()) {
+ // If we are currently setting up for an animation, hold everything until we
+ // can find out what will happen.
+ mService.mInputMethodTargetWaitingAnim = true;
+ mService.mInputMethodTarget = highestTarget;
+ return highestPos + 1;
+ } else if (highestTarget.mWinAnimator.isAnimationSet() &&
+ highestTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer) {
+ // If the window we are currently targeting is involved with an animation,
+ // and it is on top of the next target we will be over, then hold off on
+ // moving until that is done.
+ mService.mInputMethodTargetWaitingAnim = true;
+ mService.mInputMethodTarget = highestTarget;
+ return highestPos + 1;
+ }
+ }
+ }
+ }
+
+ //Slog.i(TAG_WM, "Placing input method @" + (i+1));
+ if (w != null) {
+ if (willMove) {
+ if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget + " to "
+ + w + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : ""));
+ mService.mInputMethodTarget = w;
+ mService.mInputMethodTargetWaitingAnim = false;
+ if (w.mAppToken != null) {
+ setInputMethodAnimLayerAdjustment(
+ w.mAppToken.mAppAnimator.animLayerAdjustment);
+ } else {
+ setInputMethodAnimLayerAdjustment(0);
+ }
+ }
+
+ // If the docked divider is visible, we still need to go through this whole excercise to
+ // find the appropriate input method target (used for animations and dialog
+ // adjustments), but for purposes of Z ordering we simply wish to place it above the
+ // docked divider. Unless it is already above the divider.
+ final WindowState dockedDivider = mDividerControllerLocked.getWindow();
+ if (dockedDivider != null && dockedDivider.isVisibleLw()) {
+ int dividerIndex = windows.indexOf(dockedDivider);
+ if (dividerIndex > 0 && dividerIndex > i) {
+ return dividerIndex + 1;
+ }
+ }
+ return i+1;
+ }
+ if (willMove) {
+ if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget
+ + " to null." + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : ""));
+ mService.mInputMethodTarget = null;
+ setInputMethodAnimLayerAdjustment(0);
+ }
+ return -1;
+ }
+
+ private static boolean canBeImeTarget(WindowState w) {
+ final int fl = w.mAttrs.flags & (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM);
+ final int type = w.mAttrs.type;
+
+ if (fl != 0 && fl != (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)
+ && type != TYPE_APPLICATION_STARTING) {
+ return false;
+ }
+
+ if (DEBUG_INPUT_METHOD) {
+ Slog.i(TAG_WM, "isVisibleOrAdding " + w + ": " + w.isVisibleOrAdding());
+ if (!w.isVisibleOrAdding()) {
+ Slog.i(TAG_WM, " mSurfaceController=" + w.mWinAnimator.mSurfaceController
+ + " relayoutCalled=" + w.mRelayoutCalled
+ + " viewVis=" + w.mViewVisibility
+ + " policyVis=" + w.mPolicyVisibility
+ + " policyVisAfterAnim=" + w.mPolicyVisibilityAfterAnim
+ + " parentHidden=" + w.isParentWindowHidden()
+ + " exiting=" + w.mAnimatingExit + " destroying=" + w.mDestroying);
+ if (w.mAppToken != null) {
+ Slog.i(TAG_WM, " mAppToken.hiddenRequested=" + w.mAppToken.hiddenRequested);
+ }
+ }
+ }
+ return w.isVisibleOrAdding();
+ }
+
+ private void logWindowList(final WindowList windows, String prefix) {
+ int N = windows.size();
+ while (N > 0) {
+ N--;
+ Slog.v(TAG_WM, prefix + "#" + N + ": " + windows.get(N));
+ }
+ }
+
+ boolean getNeedsMenu(WindowState win, WindowManagerPolicy.WindowState bottom) {
+ int index = -1;
+ WindowList windows = getWindowList();
+ while (true) {
+ if (win.mAttrs.needsMenuKey != NEEDS_MENU_UNSET) {
+ return win.mAttrs.needsMenuKey == NEEDS_MENU_SET_TRUE;
+ }
+ // If we reached the bottom of the range of windows we are considering,
+ // assume no menu is needed.
+ if (win == bottom) {
+ return false;
+ }
+ // The current window hasn't specified whether menu key is needed; look behind it.
+ // First, we may need to determine the starting position.
+ if (index < 0) {
+ index = windows.indexOf(win);
+ }
+ index--;
+ if (index < 0) {
+ return false;
+ }
+ win = windows.get(index);
+ }
+ }
+
void setLayoutNeeded() {
if (DEBUG_LAYOUT) Slog.w(TAG_WM, "setLayoutNeeded: callers=" + Debug.getCallers(3));
mLayoutNeeded = true;
@@ -1341,6 +1887,14 @@
}
}
+ void dumpWindowAnimators(PrintWriter pw, String subPrefix) {
+ final int count = mWindows.size();
+ for (int j = 0; j < count; j++) {
+ final WindowStateAnimator wAnim = mWindows.get(j).mWinAnimator;
+ pw.println(subPrefix + "Window #" + j + ": " + wAnim);
+ }
+ }
+
void enableSurfaceTrace(FileDescriptor fd) {
for (int i = mWindows.size() - 1; i >= 0; i--) {
final WindowState win = mWindows.get(i);
@@ -1355,6 +1909,531 @@
}
}
+ boolean checkWaitingForWindows() {
+
+ boolean haveBootMsg = false;
+ boolean haveApp = false;
+ // if the wallpaper service is disabled on the device, we're never going to have
+ // wallpaper, don't bother waiting for it
+ boolean haveWallpaper = false;
+ boolean wallpaperEnabled = mService.mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableWallpaperService)
+ && !mService.mOnlyCore;
+ boolean haveKeyguard = true;
+ final int count = mWindows.size();
+ for (int i = 0; i < count; i++) {
+ final WindowState w = mWindows.get(i);
+ if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
+ return true;
+ }
+ if (w.isDrawnLw()) {
+ if (w.mAttrs.type == TYPE_BOOT_PROGRESS) {
+ haveBootMsg = true;
+ } else if (w.mAttrs.type == TYPE_APPLICATION
+ || w.mAttrs.type == TYPE_DRAWN_APPLICATION) {
+ haveApp = true;
+ } else if (w.mAttrs.type == TYPE_WALLPAPER) {
+ haveWallpaper = true;
+ } else if (w.mAttrs.type == TYPE_STATUS_BAR) {
+ haveKeyguard = mService.mPolicy.isKeyguardDrawnLw();
+ }
+ }
+ }
+
+ if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG_WM,
+ "******** booted=" + mService.mSystemBooted
+ + " msg=" + mService.mShowingBootMessages
+ + " haveBoot=" + haveBootMsg + " haveApp=" + haveApp
+ + " haveWall=" + haveWallpaper + " wallEnabled=" + wallpaperEnabled
+ + " haveKeyguard=" + haveKeyguard);
+
+ // If we are turning on the screen to show the boot message, don't do it until the boot
+ // message is actually displayed.
+ if (!mService.mSystemBooted && !haveBootMsg) {
+ return true;
+ }
+
+ // If we are turning on the screen after the boot is completed normally, don't do so until
+ // we have the application and wallpaper.
+ if (mService.mSystemBooted && ((!haveApp && !haveKeyguard) ||
+ (wallpaperEnabled && !haveWallpaper))) {
+ return true;
+ }
+
+ return false;
+ }
+
+ void updateWindowsForAnimator(WindowAnimator animator) {
+ final WindowManagerPolicy policy = animator.mPolicy;
+ final int keyguardGoingAwayFlags = animator.mKeyguardGoingAwayFlags;
+ final boolean keyguardGoingAwayToShade =
+ (keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_TO_SHADE) != 0;
+ final boolean keyguardGoingAwayNoAnimation =
+ (keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS) != 0;
+ final boolean keyguardGoingAwayWithWallpaper =
+ (keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER) != 0;
+
+ if (animator.mKeyguardGoingAway) {
+ for (int i = mWindows.size() - 1; i >= 0; i--) {
+ WindowState win = mWindows.get(i);
+ if (!policy.isKeyguardHostWindow(win.mAttrs)) {
+ continue;
+ }
+ final WindowStateAnimator winAnimator = win.mWinAnimator;
+ if (policy.isKeyguardShowingAndNotOccluded()) {
+ if (!winAnimator.mAnimating) {
+ if (DEBUG_KEYGUARD) Slog.d(TAG,
+ "updateWindowsForAnimator: creating delay animation");
+
+ // Create a new animation to delay until keyguard is gone on its own.
+ winAnimator.mAnimation = new AlphaAnimation(1.0f, 1.0f);
+ winAnimator.mAnimation.setDuration(KEYGUARD_ANIM_TIMEOUT_MS);
+ winAnimator.mAnimationIsEntrance = false;
+ winAnimator.mAnimationStartTime = -1;
+ winAnimator.mKeyguardGoingAwayAnimation = true;
+ winAnimator.mKeyguardGoingAwayWithWallpaper
+ = keyguardGoingAwayWithWallpaper;
+ }
+ } else {
+ if (DEBUG_KEYGUARD) Slog.d(TAG,
+ "updateWindowsForAnimator: StatusBar is no longer keyguard");
+ animator.mKeyguardGoingAway = false;
+ winAnimator.clearAnimation();
+ }
+ break;
+ }
+ }
+
+ animator.mForceHiding = KEYGUARD_NOT_SHOWN;
+
+ boolean wallpaperInUnForceHiding = false;
+ boolean startingInUnForceHiding = false;
+ ArrayList<WindowStateAnimator> unForceHiding = null;
+ WindowState wallpaper = null;
+ final WallpaperController wallpaperController = mWallpaperController;
+ for (int i = mWindows.size() - 1; i >= 0; i--) {
+ WindowState win = mWindows.get(i);
+ WindowStateAnimator winAnimator = win.mWinAnimator;
+ final int flags = win.mAttrs.flags;
+ boolean canBeForceHidden = policy.canBeForceHidden(win, win.mAttrs);
+ boolean shouldBeForceHidden = animator.shouldForceHide(win);
+ if (winAnimator.hasSurface()) {
+ final boolean wasAnimating = winAnimator.mWasAnimating;
+ final boolean nowAnimating = winAnimator.stepAnimationLocked(animator.mCurrentTime);
+ winAnimator.mWasAnimating = nowAnimating;
+ animator.orAnimating(nowAnimating);
+
+ if (DEBUG_WALLPAPER) Slog.v(TAG,
+ win + ": wasAnimating=" + wasAnimating + ", nowAnimating=" + nowAnimating);
+
+ if (wasAnimating && !winAnimator.mAnimating
+ && wallpaperController.isWallpaperTarget(win)) {
+ animator.mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
+ pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ if (DEBUG_LAYOUT_REPEATS) {
+ mService.mWindowPlacerLocked.debugLayoutRepeats(
+ "updateWindowsAndWallpaperLocked 2", pendingLayoutChanges);
+ }
+ }
+
+ if (policy.isForceHiding(win.mAttrs)) {
+ if (!wasAnimating && nowAnimating) {
+ if (DEBUG_KEYGUARD || DEBUG_ANIM || DEBUG_VISIBILITY) Slog.v(TAG,
+ "Animation started that could impact force hide: " + win);
+ animator.mBulkUpdateParams |= SET_FORCE_HIDING_CHANGED;
+ pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ if (DEBUG_LAYOUT_REPEATS) {
+ mService.mWindowPlacerLocked.debugLayoutRepeats(
+ "updateWindowsAndWallpaperLocked 3", pendingLayoutChanges);
+ }
+ mService.mFocusMayChange = true;
+ } else if (animator.mKeyguardGoingAway && !nowAnimating) {
+ // Timeout!!
+ Slog.e(TAG, "Timeout waiting for animation to startup");
+ policy.startKeyguardExitAnimation(0, 0);
+ animator.mKeyguardGoingAway = false;
+ }
+ if (win.isReadyForDisplay()) {
+ if (nowAnimating && win.mWinAnimator.mKeyguardGoingAwayAnimation) {
+ animator.mForceHiding = KEYGUARD_ANIMATING_OUT;
+ } else {
+ animator.mForceHiding = win.isDrawnLw()
+ ? KEYGUARD_SHOWN : KEYGUARD_NOT_SHOWN;
+ }
+ }
+ if (DEBUG_KEYGUARD || DEBUG_VISIBILITY) Slog.v(TAG,
+ "Force hide " + animator.forceHidingToString()
+ + " hasSurface=" + win.mHasSurface
+ + " policyVis=" + win.mPolicyVisibility
+ + " destroying=" + win.mDestroying
+ + " parentHidden=" + win.isParentWindowHidden()
+ + " vis=" + win.mViewVisibility
+ + " hidden=" + win.mToken.hidden
+ + " anim=" + win.mWinAnimator.mAnimation);
+ } else if (canBeForceHidden) {
+ if (shouldBeForceHidden) {
+ if (!win.hideLw(false, false)) {
+ // Was already hidden
+ continue;
+ }
+ if (DEBUG_KEYGUARD || DEBUG_VISIBILITY) Slog.v(TAG,
+ "Now policy hidden: " + win);
+ } else {
+ final Animation postKeyguardExitAnimation =
+ animator.mPostKeyguardExitAnimation;
+ boolean applyExistingExitAnimation = postKeyguardExitAnimation != null
+ && !postKeyguardExitAnimation.hasEnded()
+ && !winAnimator.mKeyguardGoingAwayAnimation
+ && win.hasDrawnLw()
+ && !win.isChildWindow()
+ && !win.mIsImWindow
+ && isDefaultDisplay;
+
+ // If the window is already showing and we don't need to apply an existing
+ // Keyguard exit animation, skip.
+ if (!win.showLw(false, false) && !applyExistingExitAnimation) {
+ continue;
+ }
+ final boolean visibleNow = win.isVisibleNow();
+ if (!visibleNow) {
+ // Couldn't really show, must showLw() again when win becomes visible.
+ win.hideLw(false, false);
+ continue;
+ }
+ if (DEBUG_KEYGUARD || DEBUG_VISIBILITY) Slog.v(TAG,
+ "Now policy shown: " + win);
+ if ((animator.mBulkUpdateParams & SET_FORCE_HIDING_CHANGED) != 0
+ && !win.isChildWindow()) {
+ if (unForceHiding == null) {
+ unForceHiding = new ArrayList<>();
+ }
+ unForceHiding.add(winAnimator);
+ if ((flags & FLAG_SHOW_WALLPAPER) != 0) {
+ wallpaperInUnForceHiding = true;
+ }
+ if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
+ startingInUnForceHiding = true;
+ }
+ } else if (applyExistingExitAnimation) {
+ // We're already in the middle of an animation. Use the existing
+ // animation to bring in this window.
+ if (DEBUG_KEYGUARD) Slog.v(TAG,
+ "Applying existing Keyguard exit animation to new window: win="
+ + win);
+
+ final Animation a = policy.createForceHideEnterAnimation(false,
+ keyguardGoingAwayToShade);
+ winAnimator.setAnimation(a, postKeyguardExitAnimation.getStartTime(),
+ STACK_CLIP_BEFORE_ANIM);
+ winAnimator.mKeyguardGoingAwayAnimation = true;
+ winAnimator.mKeyguardGoingAwayWithWallpaper
+ = keyguardGoingAwayWithWallpaper;
+ }
+ final WindowState currentFocus = mService.mCurrentFocus;
+ if (currentFocus == null || currentFocus.mLayer < win.mLayer) {
+ // We are showing on top of the current
+ // focus, so re-evaluate focus to make
+ // sure it is correct.
+ if (DEBUG_FOCUS_LIGHT) Slog.v(TAG,
+ "updateWindowsForAnimator: setting mFocusMayChange true");
+ mService.mFocusMayChange = true;
+ }
+ }
+ if ((flags & FLAG_SHOW_WALLPAPER) != 0) {
+ animator.mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
+ pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ if (DEBUG_LAYOUT_REPEATS) {
+ mService.mWindowPlacerLocked.debugLayoutRepeats(
+ "updateWindowsAndWallpaperLocked 4", pendingLayoutChanges);
+ }
+ }
+ }
+ }
+
+ // If the window doesn't have a surface, the only thing we care about is the correct
+ // policy visibility.
+ else if (canBeForceHidden) {
+ if (shouldBeForceHidden) {
+ win.hideLw(false, false);
+ } else {
+ win.showLw(false, false);
+ }
+ }
+
+ final AppWindowToken atoken = win.mAppToken;
+ if (winAnimator.mDrawState == READY_TO_SHOW) {
+ if (atoken == null || atoken.allDrawn) {
+ if (win.performShowLocked()) {
+ pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
+ if (DEBUG_LAYOUT_REPEATS) {
+ mService.mWindowPlacerLocked.debugLayoutRepeats(
+ "updateWindowsAndWallpaperLocked 5", pendingLayoutChanges);
+ }
+ }
+ }
+ }
+ final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
+ if (appAnimator != null && appAnimator.thumbnail != null) {
+ if (appAnimator.thumbnailTransactionSeq != animator.mAnimTransactionSequence) {
+ appAnimator.thumbnailTransactionSeq = animator.mAnimTransactionSequence;
+ appAnimator.thumbnailLayer = 0;
+ }
+ if (appAnimator.thumbnailLayer < winAnimator.mAnimLayer) {
+ appAnimator.thumbnailLayer = winAnimator.mAnimLayer;
+ }
+ }
+ if (win.mIsWallpaper) {
+ wallpaper = win;
+ }
+ } // end forall windows
+
+ // If we have windows that are being shown due to them no longer being force-hidden, apply
+ // the appropriate animation to them if animations are not disabled.
+ if (unForceHiding != null) {
+ if (!keyguardGoingAwayNoAnimation) {
+ boolean first = true;
+ for (int i=unForceHiding.size()-1; i>=0; i--) {
+ final WindowStateAnimator winAnimator = unForceHiding.get(i);
+ final Animation a = policy.createForceHideEnterAnimation(
+ wallpaperInUnForceHiding && !startingInUnForceHiding,
+ keyguardGoingAwayToShade);
+ if (a != null) {
+ if (DEBUG_KEYGUARD) Slog.v(TAG,
+ "Starting keyguard exit animation on window " + winAnimator.mWin);
+ winAnimator.setAnimation(a, STACK_CLIP_BEFORE_ANIM);
+ winAnimator.mKeyguardGoingAwayAnimation = true;
+ winAnimator.mKeyguardGoingAwayWithWallpaper
+ = keyguardGoingAwayWithWallpaper;
+ if (first) {
+ animator.mPostKeyguardExitAnimation = a;
+ animator.mPostKeyguardExitAnimation.setStartTime(animator.mCurrentTime);
+ first = false;
+ }
+ }
+ }
+ } else if (animator.mKeyguardGoingAway) {
+ policy.startKeyguardExitAnimation(animator.mCurrentTime, 0 /* duration */);
+ animator.mKeyguardGoingAway = false;
+ }
+
+
+ // Wallpaper is going away in un-force-hide motion, animate it as well.
+ if (!wallpaperInUnForceHiding && wallpaper != null && !keyguardGoingAwayNoAnimation) {
+ if (DEBUG_KEYGUARD) Slog.d(TAG,
+ "updateWindowsForAnimator: wallpaper animating away");
+ final Animation a = policy.createForceHideWallpaperExitAnimation(
+ keyguardGoingAwayToShade);
+ if (a != null) {
+ wallpaper.mWinAnimator.setAnimation(a);
+ }
+ }
+ }
+
+ if (animator.mPostKeyguardExitAnimation != null) {
+ // We're in the midst of a keyguard exit animation.
+ if (animator.mKeyguardGoingAway) {
+ policy.startKeyguardExitAnimation(animator.mCurrentTime +
+ animator.mPostKeyguardExitAnimation.getStartOffset(),
+ animator.mPostKeyguardExitAnimation.getDuration());
+ animator.mKeyguardGoingAway = false;
+ }
+ // mPostKeyguardExitAnimation might either be ended normally, cancelled, or "orphaned",
+ // meaning that the window it was running on was removed. We check for hasEnded() for
+ // ended normally and cancelled case, and check the time for the "orphaned" case.
+ else if (animator.mPostKeyguardExitAnimation.hasEnded()
+ || animator.mCurrentTime - animator.mPostKeyguardExitAnimation.getStartTime()
+ > animator.mPostKeyguardExitAnimation.getDuration()) {
+ // Done with the animation, reset.
+ if (DEBUG_KEYGUARD) Slog.v(TAG, "Done with Keyguard exit animations.");
+ animator.mPostKeyguardExitAnimation = null;
+ }
+ }
+
+ final WindowState winShowWhenLocked = (WindowState) policy.getWinShowWhenLockedLw();
+ if (winShowWhenLocked != null) {
+ animator.mLastShowWinWhenLocked = winShowWhenLocked;
+ }
+ }
+
+ void updateWallpaperForAnimator(WindowAnimator animator) {
+ resetAnimationBackgroundAnimator();
+
+ final WindowList windows = mWindows;
+ WindowState detachedWallpaper = null;
+
+ for (int i = windows.size() - 1; i >= 0; i--) {
+ final WindowState win = windows.get(i);
+ final WindowStateAnimator winAnimator = win.mWinAnimator;
+ if (winAnimator.mSurfaceController == null || !winAnimator.hasSurface()) {
+ continue;
+ }
+
+ final int flags = win.mAttrs.flags;
+
+ // If this window is animating, make a note that we have an animating window and take
+ // care of a request to run a detached wallpaper animation.
+ if (winAnimator.mAnimating) {
+ if (winAnimator.mAnimation != null) {
+ if ((flags & FLAG_SHOW_WALLPAPER) != 0
+ && winAnimator.mAnimation.getDetachWallpaper()) {
+ detachedWallpaper = win;
+ }
+ final int color = winAnimator.mAnimation.getBackgroundColor();
+ if (color != 0) {
+ final TaskStack stack = win.getStack();
+ if (stack != null) {
+ stack.setAnimationBackground(winAnimator, color);
+ }
+ }
+ }
+ animator.setAnimating(true);
+ }
+
+ // If this window's app token is running a detached wallpaper animation, make a note so
+ // we can ensure the wallpaper is displayed behind it.
+ final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
+ if (appAnimator != null && appAnimator.animation != null
+ && appAnimator.animating) {
+ if ((flags & FLAG_SHOW_WALLPAPER) != 0
+ && appAnimator.animation.getDetachWallpaper()) {
+ detachedWallpaper = win;
+ }
+
+ final int color = appAnimator.animation.getBackgroundColor();
+ if (color != 0) {
+ final TaskStack stack = win.getStack();
+ if (stack != null) {
+ stack.setAnimationBackground(winAnimator, color);
+ }
+ }
+ }
+ } // end forall windows
+
+ if (animator.mWindowDetachedWallpaper != detachedWallpaper) {
+ if (DEBUG_WALLPAPER) Slog.v(TAG, "Detached wallpaper changed from "
+ + animator.mWindowDetachedWallpaper + " to " + detachedWallpaper);
+ animator.mWindowDetachedWallpaper = detachedWallpaper;
+ animator.mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
+ }
+ }
+
+ void prepareWindowSurfaces() {
+ final int count = mWindows.size();
+ for (int j = 0; j < count; j++) {
+ mWindows.get(j).mWinAnimator.prepareSurfaceLocked(true);
+ }
+ }
+
+ boolean inputMethodClientHasFocus(IInputMethodClient client) {
+ // The focus for the client is the window immediately below where we would place the input
+ // method window.
+ int idx = findDesiredInputMethodWindowIndex(false);
+ if (idx <= 0) {
+ return false;
+ }
+
+ WindowState imFocus = mWindows.get(idx - 1);
+ if (DEBUG_INPUT_METHOD) {
+ Slog.i(TAG_WM, "Desired input method target: " + imFocus);
+ Slog.i(TAG_WM, "Current focus: " + mService.mCurrentFocus);
+ Slog.i(TAG_WM, "Last focus: " + mService.mLastFocus);
+ }
+
+ if (imFocus == null) {
+ return false;
+ }
+
+ // This may be a starting window, in which case we still want to count it as okay.
+ if (imFocus.mAttrs.type == TYPE_APPLICATION_STARTING && imFocus.mAppToken != null) {
+ // The client has definitely started, so it really should have a window in this app
+ // token. Let's look for it.
+ final WindowState w = imFocus.mAppToken.getFirstNonStartingWindow();
+ if (w != null) {
+ if (DEBUG_INPUT_METHOD) Slog.i(TAG_WM, "Switching to real app window: " + w);
+ imFocus = w;
+ }
+ }
+
+ final IInputMethodClient imeClient = imFocus.mSession.mClient;
+
+ if (DEBUG_INPUT_METHOD) {
+ Slog.i(TAG_WM, "IM target client: " + imeClient);
+ if (imeClient != null) {
+ Slog.i(TAG_WM, "IM target client binder: " + imeClient.asBinder());
+ Slog.i(TAG_WM, "Requesting client binder: " + client.asBinder());
+ }
+ }
+
+ return imeClient != null && imeClient.asBinder() == client.asBinder();
+ }
+
+ boolean hasSecureWindowOnScreen() {
+ for (int i = mWindows.size() - 1; i >= 0; --i) {
+ final WindowState ws = mWindows.get(i);
+ if (ws.isOnScreen() && (ws.mAttrs.flags & FLAG_SECURE) != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void updateSystemUiVisibility(int visibility, int globalDiff) {
+ for (int i = mWindows.size() - 1; i >= 0; --i) {
+ final WindowState ws = mWindows.get(i);
+ try {
+ int curValue = ws.mSystemUiVisibility;
+ int diff = (curValue ^ visibility) & globalDiff;
+ int newValue = (curValue & ~diff) | (visibility & diff);
+ if (newValue != curValue) {
+ ws.mSeq++;
+ ws.mSystemUiVisibility = newValue;
+ }
+ if (newValue != curValue || ws.mAttrs.hasSystemUiListeners) {
+ ws.mClient.dispatchSystemUiVisibilityChanged(ws.mSeq,
+ visibility, newValue, diff);
+ }
+ } catch (RemoteException e) {
+ // so sorry
+ }
+ }
+ }
+
+ void onWindowFreezeTimeout() {
+ Slog.w(TAG_WM, "Window freeze timeout expired.");
+ mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
+ for (int i = mWindows.size() - 1; i >= 0; --i) {
+ final WindowState w = mWindows.get(i);
+ if (!w.mOrientationChanging) {
+ continue;
+ }
+ w.mOrientationChanging = false;
+ w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+ - mService.mDisplayFreezeTime);
+ Slog.w(TAG_WM, "Force clearing orientation change: " + w);
+ }
+ mService.mWindowPlacerLocked.performSurfacePlacement();
+ }
+
+ void waitForAllWindowsDrawn() {
+ final WindowManagerPolicy policy = mService.mPolicy;
+ for (int winNdx = mWindows.size() - 1; winNdx >= 0; --winNdx) {
+ final WindowState win = mWindows.get(winNdx);
+ final boolean isForceHiding = policy.isForceHiding(win.mAttrs);
+ final boolean keyguard = policy.isKeyguardHostWindow(win.mAttrs);
+ if (win.isVisibleLw() && (win.mAppToken != null || isForceHiding || keyguard)) {
+ win.mWinAnimator.mDrawState = DRAW_PENDING;
+ // Force add to mResizingWindows.
+ win.mLastContentInsets.set(-1, -1, -1, -1);
+ mService.mWaitingForDrawn.add(win);
+
+ // No need to wait for the windows below Keyguard.
+ if (isForceHiding) {
+ return;
+ }
+ }
+ }
+ }
+
static final class GetWindowOnDisplaySearchResult {
boolean reachedToken;
WindowState foundWindow;
@@ -1451,6 +2530,37 @@
setLayoutNeeded();
}
+ @Override
+ int getOrientation() {
+ if (mService.isStackVisibleLocked(DOCKED_STACK_ID)
+ || mService.isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) {
+ // Apps and their containers are not allowed to specify an orientation while the
+ // docked or freeform stack is visible...except for the home stack/task if the
+ // docked stack is minimized and it actually set something.
+ if (mHomeStack != null && mHomeStack.isVisible()
+ && mDividerControllerLocked.isMinimizedDock()) {
+ final int orientation = mHomeStack.getOrientation();
+ if (orientation != SCREEN_ORIENTATION_UNSET) {
+ return orientation;
+ }
+ }
+ return SCREEN_ORIENTATION_UNSPECIFIED;
+ }
+
+ final int orientation = super.getOrientation();
+ if (orientation != SCREEN_ORIENTATION_UNSET
+ && orientation != SCREEN_ORIENTATION_BEHIND) {
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
+ "App is requesting an orientation, return " + orientation);
+ return orientation;
+ }
+
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
+ "No app is requesting an orientation, return " + mService.mLastOrientation);
+ // The next app has not been requested to be visible, so we keep the current orientation
+ // to prevent freezing/unfreezing the display too early.
+ return mService.mLastOrientation;
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 5854197..f75f224 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -33,6 +33,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED;
+import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
import android.content.Context;
import android.content.res.Configuration;
@@ -296,7 +297,7 @@
}
}
- boolean wasVisible() {
+ private boolean wasVisible() {
return mLastVisibility;
}
@@ -356,7 +357,7 @@
mLastRect.set(frame);
}
- void notifyDockedDividerVisibilityChanged(boolean visible) {
+ private void notifyDockedDividerVisibilityChanged(boolean visible) {
final int size = mDockedStackListeners.beginBroadcast();
for (int i = 0; i < size; ++i) {
final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
@@ -370,6 +371,7 @@
}
void notifyDockedStackExistsChanged(boolean exists) {
+ // TODO(multi-display): Perform all actions only for current display.
final int size = mDockedStackListeners.beginBroadcast();
for (int i = 0; i < size; ++i) {
final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
@@ -413,7 +415,7 @@
return mImeHideRequested;
}
- void notifyDockedStackMinimizedChanged(boolean minimizedDock, long animDuration) {
+ private void notifyDockedStackMinimizedChanged(boolean minimizedDock, long animDuration) {
mService.mH.removeMessages(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED);
mService.mH.obtainMessage(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED,
minimizedDock ? 1 : 0, 0).sendToTarget();
@@ -442,7 +444,7 @@
mDockedStackListeners.finishBroadcast();
}
- void notifyAdjustedForImeChanged(boolean adjustedForIme, long animDuration) {
+ private void notifyAdjustedForImeChanged(boolean adjustedForIme, long animDuration) {
final int size = mDockedStackListeners.beginBroadcast();
for (int i = 0; i < size; ++i) {
final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
@@ -474,8 +476,7 @@
stack.getDimBounds(mTmpRect);
if (mTmpRect.height() > 0 && mTmpRect.width() > 0) {
mDimLayer.setBounds(mTmpRect);
- mDimLayer.show(mService.mLayersController.getResizeDimLayer(),
- alpha, 0 /* duration */);
+ mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
} else {
visibleAndValid = false;
}
@@ -487,6 +488,14 @@
}
/**
+ * @return The layer used for dimming the apps when dismissing docked/fullscreen stack. Just
+ * above all application surfaces.
+ */
+ private int getResizeDimLayer() {
+ return (mWindow != null) ? mWindow.mLayer - 1 : LAYER_OFFSET_DIM;
+ }
+
+ /**
* Notifies the docked stack divider controller of a visibility change that happens without
* an animation.
*/
@@ -693,7 +702,7 @@
return animateForIme(now);
} else {
if (mDimLayer != null && mDimLayer.isDimming()) {
- mDimLayer.setLayer(mService.mLayersController.getResizeDimLayer());
+ mDimLayer.setLayer(getResizeDimLayer());
}
return false;
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 065a3dc..6a06ef3 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
@@ -384,7 +385,8 @@
/* Notifies that the input device configuration has changed. */
@Override
public void notifyConfigurationChanged() {
- mService.sendNewConfiguration();
+ // TODO(multi-display): Notify proper displays that are associated with this input device.
+ mService.sendNewConfiguration(DEFAULT_DISPLAY);
synchronized (mInputDevicesReadyMonitor) {
if (!mInputDevicesReady) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 05ffe2d..90e27ea 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -165,8 +165,13 @@
ParcelFileDescriptor mSurfaceTraceFd;
RemoteEventTrace mRemoteEventTrace;
+ private final WindowLayersController mLayersController;
+ final WallpaperController mWallpaperController;
+
RootWindowContainer(WindowManagerService service) {
mService = service;
+ mLayersController = new WindowLayersController(mService);
+ mWallpaperController = new WallpaperController(mService);
}
WindowState computeFocusedWindow() {
@@ -211,7 +216,8 @@
}
private DisplayContent createDisplayContent(final Display display) {
- final DisplayContent dc = new DisplayContent(display, mService);
+ final DisplayContent dc = new DisplayContent(display, mService, mLayersController,
+ mWallpaperController);
final int displayId = display.getDisplayId();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display);
@@ -230,7 +236,7 @@
mService.configureDisplayPolicyLocked(dc);
// TODO(multi-display): Create an input channel for each display with touch capability.
- if (displayId == Display.DEFAULT_DISPLAY) {
+ if (displayId == DEFAULT_DISPLAY) {
dc.mTapDetector = new TaskTapPointerEventListener(
mService, dc);
mService.registerPointerEventListener(dc.mTapDetector);
@@ -268,8 +274,7 @@
mService.mStackIdToStack.put(stackId, stack);
if (stackId == DOCKED_STACK_ID) {
- mService.getDefaultDisplayContentLocked().mDividerControllerLocked
- .notifyDockedStackExistsChanged(true);
+ dc.mDividerControllerLocked.notifyDockedStackExistsChanged(true);
}
}
@@ -439,10 +444,10 @@
// TODO(multi-display): By default we add this to the default display, but maybe we
// should provide an API for a token to be added to any display?
- final WindowToken token = new WindowToken(mService, binder, type, true,
- getDisplayContent(DEFAULT_DISPLAY));
+ final DisplayContent dc = getDisplayContent(DEFAULT_DISPLAY);
+ final WindowToken token = new WindowToken(mService, binder, type, true, dc);
if (type == TYPE_WALLPAPER) {
- mService.mWallpaperControllerLocked.addWallpaperToken(token);
+ dc.mWallpaperController.addWallpaperToken(token);
}
}
@@ -563,8 +568,33 @@
}
}
- /** Set new config and return array of ids of stacks that were changed during update. */
- int[] setGlobalConfigurationIfNeeded(Configuration newConfiguration) {
+ /**
+ * Set new display override config and return array of ids of stacks that were changed during
+ * update. If called for the default display, global configuration will also be updated.
+ */
+ int[] setDisplayOverrideConfigurationIfNeeded(Configuration newConfiguration, int displayId) {
+ final DisplayContent displayContent = getDisplayContent(displayId);
+ if (displayContent == null) {
+ throw new IllegalArgumentException("Display not found for id: " + displayId);
+ }
+
+ final Configuration currentConfig = displayContent.getOverrideConfiguration();
+ final boolean configChanged = currentConfig.diff(newConfiguration) != 0;
+ if (!configChanged) {
+ return null;
+ }
+ displayContent.onOverrideConfigurationChanged(currentConfig);
+
+ if (displayId == DEFAULT_DISPLAY) {
+ // Override configuration of the default display duplicates global config. In this case
+ // we also want to update the global config.
+ return setGlobalConfigurationIfNeeded(newConfiguration);
+ } else {
+ return updateStackBoundsAfterConfigChange(displayId);
+ }
+ }
+
+ private int[] setGlobalConfigurationIfNeeded(Configuration newConfiguration) {
final boolean configChanged = getConfiguration().diff(newConfiguration) != 0;
if (!configChanged) {
return null;
@@ -597,6 +627,16 @@
return mChangedStackList.isEmpty() ? null : ArrayUtils.convertToIntArray(mChangedStackList);
}
+ /** Same as {@link #updateStackBoundsAfterConfigChange()} but only for a specific display. */
+ private int[] updateStackBoundsAfterConfigChange(int displayId) {
+ mChangedStackList.clear();
+
+ final DisplayContent dc = getDisplayContent(displayId);
+ dc.updateStackBoundsAfterConfigChange(mChangedStackList);
+
+ return mChangedStackList.isEmpty() ? null : ArrayUtils.convertToIntArray(mChangedStackList);
+ }
+
private void prepareFreezingTaskBounds() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
mChildren.get(i).prepareFreezingTaskBounds();
@@ -715,7 +755,6 @@
boolean addPipInputConsumerHandle = pipInputConsumer != null;
boolean addWallpaperInputConsumerHandle = wallpaperInputConsumer != null;
final Rect pipTouchableBounds = addPipInputConsumerHandle ? new Rect() : null;
- final WallpaperController wallpaperController = mService.mWallpaperControllerLocked;
boolean disableWallpaperTouchEvents = false;
final int count = mChildren.size();
@@ -766,7 +805,7 @@
if ((privateFlags & PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS) != 0) {
disableWallpaperTouchEvents = true;
}
- final boolean hasWallpaper = wallpaperController.isWallpaperTarget(child)
+ final boolean hasWallpaper = dc.mWallpaperController.isWallpaperTarget(child)
&& (privateFlags & PRIVATE_FLAG_KEYGUARD) == 0
&& !disableWallpaperTouchEvents;
@@ -948,14 +987,13 @@
"<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
}
- final WindowList defaultWindows = defaultDisplay.getWindowList();
final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
// If we are ready to perform an app transition, check through all of the app tokens to be
// shown and see if they are ready to go.
if (mService.mAppTransition.isReady()) {
defaultDisplay.pendingLayoutChanges |=
- surfacePlacer.handleAppTransitionReadyLocked(defaultWindows);
+ surfacePlacer.handleAppTransitionReadyLocked();
if (DEBUG_LAYOUT_REPEATS)
surfacePlacer.debugLayoutRepeats("after handleAppTransitionReadyLocked",
defaultDisplay.pendingLayoutChanges);
@@ -1043,7 +1081,7 @@
if (mService.mInputMethodWindow == win) {
mService.mInputMethodWindow = null;
}
- if (mService.mWallpaperControllerLocked.isWallpaperTarget(win)) {
+ if (win.getDisplayContent().mWallpaperController.isWallpaperTarget(win)) {
wallpaperDestroyed = true;
}
win.destroyOrSaveSurface();
@@ -1060,7 +1098,7 @@
if (!token.hasVisible) {
exitingTokens.remove(i);
if (token.windowType == TYPE_WALLPAPER) {
- mService.mWallpaperControllerLocked.removeWallpaperToken(token);
+ displayContent.mWallpaperController.removeWallpaperToken(token);
}
}
}
@@ -1141,8 +1179,10 @@
if (mUpdateRotation) {
if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
- if (mService.updateRotationUncheckedLocked(false)) {
- mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);
+ // TODO(multi-display): Update rotation for different displays separately.
+ final int displayId = defaultDisplay.getDisplayId();
+ if (mService.updateRotationUncheckedLocked(false, displayId)) {
+ mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
} else {
mUpdateRotation = false;
}
@@ -1171,9 +1211,9 @@
}
}
- for (DisplayContent displayContent : displayList) {
- mService.mLayersController.assignLayersLocked(displayContent.getWindowList());
- displayContent.setLayoutNeeded();
+ for (int j = displayList.size() - 1; j >= 0; --j) {
+ final DisplayContent dc = displayList.get(j);
+ dc.assignWindowLayers(true /*setLayoutNeeded*/);
}
}
@@ -1227,7 +1267,7 @@
final int displayId = dc.getDisplayId();
final int dw = displayInfo.logicalWidth;
final int dh = displayInfo.logicalHeight;
- final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+ final boolean isDefaultDisplay = (displayId == DEFAULT_DISPLAY);
final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
// Reset for each display.
@@ -1248,18 +1288,16 @@
if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats(
"On entry to LockedInner", dc.pendingLayoutChanges);
- if ((dc.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0
- && mService.mWallpaperControllerLocked.adjustWallpaperWindows()) {
- mService.mLayersController.assignLayersLocked(windows);
- dc.setLayoutNeeded();
+ if ((dc.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
+ dc.adjustWallpaperWindows();
}
if (isDefaultDisplay
&& (dc.pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {
if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
- if (mService.updateOrientationFromAppTokensLocked(true)) {
+ if (mService.updateOrientationFromAppTokensLocked(true, displayId)) {
dc.setLayoutNeeded();
- mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);
+ mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
}
}
@@ -1316,10 +1354,10 @@
w.applyDimLayerIfNeeded();
if (isDefaultDisplay && obscuredChanged && w.isVisibleLw()
- && mService.mWallpaperControllerLocked.isWallpaperTarget(w)) {
+ && dc.mWallpaperController.isWallpaperTarget(w)) {
// This is the wallpaper target and its obscured state changed... make sure the
// current wallpaper's visibility has been updated accordingly.
- mService.mWallpaperControllerLocked.updateWallpaperVisibility();
+ dc.mWallpaperController.updateWallpaperVisibility();
}
w.handleWindowMovedIfNeeded();
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index edd3a3b..ead70e1 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -58,26 +58,25 @@
* This class represents an active client session. There is generally one
* Session object per process that is interacting with the window manager.
*/
-final class Session extends IWindowSession.Stub
+// Needs to be public and not final so we can mock during tests...sucks I know :(
+public class Session extends IWindowSession.Stub
implements IBinder.DeathRecipient {
final WindowManagerService mService;
final IWindowSessionCallback mCallback;
final IInputMethodClient mClient;
- final IInputContext mInputContext;
final int mUid;
final int mPid;
- final String mStringName;
+ private final String mStringName;
SurfaceSession mSurfaceSession;
- int mNumWindow = 0;
- boolean mClientDead = false;
- float mLastReportedAnimatorScale;
+ private int mNumWindow = 0;
+ private boolean mClientDead = false;
+ private float mLastReportedAnimatorScale;
public Session(WindowManagerService service, IWindowSessionCallback callback,
IInputMethodClient client, IInputContext inputContext) {
mService = service;
mCallback = callback;
mClient = client;
- mInputContext = inputContext;
mUid = Binder.getCallingUid();
mPid = Binder.getCallingPid();
mLastReportedAnimatorScale = service.getCurrentAnimatorScale();
@@ -461,7 +460,7 @@
synchronized(mService.mWindowMap) {
long ident = Binder.clearCallingIdentity();
try {
- mService.mWallpaperControllerLocked.setWindowWallpaperPosition(
+ mService.mRoot.mWallpaperController.setWindowWallpaperPosition(
mService.windowForClientLocked(this, window, true),
x, y, xStep, yStep);
} finally {
@@ -472,7 +471,7 @@
public void wallpaperOffsetsComplete(IBinder window) {
synchronized (mService.mWindowMap) {
- mService.mWallpaperControllerLocked.wallpaperOffsetsComplete(window);
+ mService.mRoot.mWallpaperController.wallpaperOffsetsComplete(window);
}
}
@@ -480,7 +479,7 @@
synchronized(mService.mWindowMap) {
long ident = Binder.clearCallingIdentity();
try {
- mService.mWallpaperControllerLocked.setWindowWallpaperDisplayOffset(
+ mService.mRoot.mWallpaperController.setWindowWallpaperDisplayOffset(
mService.windowForClientLocked(this, window, true), x, y);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -493,7 +492,7 @@
synchronized(mService.mWindowMap) {
long ident = Binder.clearCallingIdentity();
try {
- return mService.mWallpaperControllerLocked.sendWindowWallpaperCommand(
+ return mService.mRoot.mWallpaperController.sendWindowWallpaperCommand(
mService.windowForClientLocked(this, window, true),
action, x, y, z, extras, sync);
} finally {
@@ -504,7 +503,7 @@
public void wallpaperCommandComplete(IBinder window, Bundle result) {
synchronized (mService.mWindowMap) {
- mService.mWallpaperControllerLocked.wallpaperCommandComplete(window);
+ mService.mRoot.mWallpaperController.wallpaperCommandComplete(window);
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 19c9b7d..7f543f9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -327,8 +327,9 @@
* the adjusted bounds's top.
*/
void alignToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds, boolean alignBottom) {
- final Configuration overrideConfig = getOverrideConfiguration();
- if (!isResizeable() || Configuration.EMPTY.equals(overrideConfig)) {
+ // Task override config might be empty, while display or stack override config isn't, so
+ // we have to check merged override config here.
+ if (!isResizeable() || Configuration.EMPTY.equals(getMergedOverrideConfiguration())) {
return;
}
@@ -340,7 +341,7 @@
mTmpRect2.offsetTo(adjustedBounds.left, adjustedBounds.top);
}
setTempInsetBounds(tempInsetBounds);
- resizeLocked(mTmpRect2, overrideConfig, false /* forced */);
+ resizeLocked(mTmpRect2, getOverrideConfiguration(), false /* forced */);
}
/** Return true if the current bound can get outputted to the rest of the system as-is. */
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 7c39bd2..5402f0a 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -35,6 +35,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.RESIZE_STACK;
+import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
import android.app.ActivityManager.StackId;
import android.content.res.Configuration;
@@ -447,8 +448,7 @@
// Calculate the current position.
final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
- final int dividerSize = mService.getDefaultDisplayContentLocked()
- .getDockedDividerController().getContentWidth();
+ final int dividerSize = mDisplayContent.getDockedDividerController().getContentWidth();
final int dockSide = getDockSide(outBounds);
final int dividerPosition = DockedDividerUtils.calculatePositionForBounds(outBounds,
dockSide, dividerSize);
@@ -782,13 +782,14 @@
mAnimationBackgroundSurface.destroySurface();
mAnimationBackgroundSurface = null;
}
+ final DockedStackDividerController dividerController =
+ mDisplayContent.mDividerControllerLocked;
mDisplayContent = null;
mService.mWindowPlacerLocked.requestTraversal();
if (mStackId == DOCKED_STACK_ID) {
- mService.getDefaultDisplayContentLocked().mDividerControllerLocked
- .notifyDockedStackExistsChanged(false);
+ dividerController.notifyDockedStackExistsChanged(false);
}
}
@@ -802,8 +803,8 @@
if (mAnimationBackgroundAnimator == null
|| animLayer < mAnimationBackgroundAnimator.mAnimLayer) {
mAnimationBackgroundAnimator = winAnimator;
- animLayer = mService.adjustAnimationBackground(winAnimator);
- mAnimationBackgroundSurface.show(animLayer - WindowManagerService.LAYER_OFFSET_DIM,
+ animLayer = mDisplayContent.getLayerForAnimationBackground(winAnimator);
+ mAnimationBackgroundSurface.show(animLayer - LAYER_OFFSET_DIM,
((color >> 24) & 0xff) / 255f, 0);
}
}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 0841231..4ab8887 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -628,10 +628,9 @@
return changed;
}
- boolean adjustWallpaperWindows() {
+ boolean adjustWallpaperWindows(WindowList windows) {
mService.mRoot.mWallpaperMayChange = false;
- final WindowList windows = mService.getDefaultWindowListLocked();
// First find top-most window that has asked to be on top of the wallpaper;
// all wallpapers go behind it.
findWallpaperTarget(windows, mFindResults);
@@ -726,8 +725,8 @@
* Adjusts the wallpaper windows if the input display has a pending wallpaper layout or one of
* the opening apps should be a wallpaper target.
*/
- void adjustWallpaperWindowsForAppTransitionIfNeeded(
- DisplayContent dc, ArraySet<AppWindowToken> openingApps, WindowList windows) {
+ void adjustWallpaperWindowsForAppTransitionIfNeeded(DisplayContent dc,
+ ArraySet<AppWindowToken> openingApps) {
boolean adjust = false;
if ((dc.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
adjust = true;
@@ -741,9 +740,8 @@
}
}
- if (adjust && adjustWallpaperWindows()) {
- mService.mLayersController.assignLayersLocked(windows);
- dc.setLayoutNeeded();
+ if (adjust) {
+ dc.adjustWallpaperWindows();
}
}
@@ -769,6 +767,10 @@
pw.print("mLastWallpaperDisplayOffsetX="); pw.print(mLastWallpaperDisplayOffsetX);
pw.print(" mLastWallpaperDisplayOffsetY="); pw.println(mLastWallpaperDisplayOffsetY);
}
+
+ if (mWallpaperAnimLayerAdjustment != 0) {
+ pw.println(prefix + "mWallpaperAnimLayerAdjustment=" + mWallpaperAnimLayerAdjustment);
+ }
}
void dumpTokens(PrintWriter pw, String prefix, boolean dumpAll) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index fb6b09a..4c62245 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -16,28 +16,17 @@
package com.android.server.wm;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
-import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
-import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEYGUARD;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
-import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
-import static com.android.server.wm.WindowSurfacePlacer.SET_FORCE_HIDING_CHANGED;
import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE;
@@ -48,14 +37,11 @@
import android.util.SparseArray;
import android.util.TimeUtils;
import android.view.Choreographer;
-import android.view.Display;
import android.view.SurfaceControl;
import android.view.WindowManagerPolicy;
-import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import java.io.PrintWriter;
-import java.util.ArrayList;
/**
* Singleton class that carries out the animations and Surface operations in a separate task
@@ -65,7 +51,7 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowAnimator" : TAG_WM;
/** How long to give statusbar to clear the private keyguard flag when animating out */
- private static final long KEYGUARD_ANIM_TIMEOUT_MS = 1000;
+ static final long KEYGUARD_ANIM_TIMEOUT_MS = 1000;
final WindowManagerService mService;
final Context mContext;
@@ -85,7 +71,7 @@
/** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this
* is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
- private int mAnimTransactionSequence;
+ int mAnimTransactionSequence;
/** Window currently running an animation that has requested it be detached
* from the wallpaper. This means we need to ensure the wallpaper is
@@ -120,9 +106,9 @@
private final AppTokenList mTmpExitingAppTokens = new AppTokenList();
/** The window that was previously hiding the Keyguard. */
- private WindowState mLastShowWinWhenLocked;
+ WindowState mLastShowWinWhenLocked;
- private String forceHidingToString() {
+ String forceHidingToString() {
switch (mForceHiding) {
case KEYGUARD_NOT_SHOWN: return "KEYGUARD_NOT_SHOWN";
case KEYGUARD_SHOWN: return "KEYGUARD_SHOWN";
@@ -150,7 +136,7 @@
void addDisplayLocked(final int displayId) {
// Create the DisplayContentsAnimator object by retrieving it.
getDisplayContentsAnimatorLocked(displayId);
- if (displayId == Display.DEFAULT_DISPLAY) {
+ if (displayId == DEFAULT_DISPLAY) {
mInitialized = true;
}
}
@@ -184,7 +170,7 @@
return null;
}
- private boolean shouldForceHide(WindowState win) {
+ boolean shouldForceHide(WindowState win) {
final WindowState imeTarget = mService.mInputMethodTarget;
final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisibleNow() &&
((imeTarget.getAttrs().flags & FLAG_SHOW_WHEN_LOCKED) != 0
@@ -220,377 +206,10 @@
&& !mKeyguardAnimatingIn;
boolean hideDockDivider = win.mAttrs.type == TYPE_DOCK_DIVIDER
&& win.getDisplayContent().getDockedStackLocked() == null;
- return keyguardOn && !allowWhenLocked && (win.getDisplayId() == Display.DEFAULT_DISPLAY)
+ return keyguardOn && !allowWhenLocked && (win.getDisplayId() == DEFAULT_DISPLAY)
|| hideDockDivider;
}
- private void updateWindowsLocked(final int displayId) {
- ++mAnimTransactionSequence;
-
- final WindowList windows = mService.getWindowListLocked(displayId);
-
- final boolean keyguardGoingAwayToShade =
- (mKeyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_TO_SHADE) != 0;
- final boolean keyguardGoingAwayNoAnimation =
- (mKeyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS) != 0;
- final boolean keyguardGoingAwayWithWallpaper =
- (mKeyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER) != 0;
-
- if (mKeyguardGoingAway) {
- for (int i = windows.size() - 1; i >= 0; i--) {
- WindowState win = windows.get(i);
- if (!mPolicy.isKeyguardHostWindow(win.mAttrs)) {
- continue;
- }
- final WindowStateAnimator winAnimator = win.mWinAnimator;
- if (mPolicy.isKeyguardShowingAndNotOccluded()) {
- if (!winAnimator.mAnimating) {
- if (DEBUG_KEYGUARD) Slog.d(TAG,
- "updateWindowsLocked: creating delay animation");
-
- // Create a new animation to delay until keyguard is gone on its own.
- winAnimator.mAnimation = new AlphaAnimation(1.0f, 1.0f);
- winAnimator.mAnimation.setDuration(KEYGUARD_ANIM_TIMEOUT_MS);
- winAnimator.mAnimationIsEntrance = false;
- winAnimator.mAnimationStartTime = -1;
- winAnimator.mKeyguardGoingAwayAnimation = true;
- winAnimator.mKeyguardGoingAwayWithWallpaper
- = keyguardGoingAwayWithWallpaper;
- }
- } else {
- if (DEBUG_KEYGUARD) Slog.d(TAG,
- "updateWindowsLocked: StatusBar is no longer keyguard");
- mKeyguardGoingAway = false;
- winAnimator.clearAnimation();
- }
- break;
- }
- }
-
- mForceHiding = KEYGUARD_NOT_SHOWN;
-
- boolean wallpaperInUnForceHiding = false;
- boolean startingInUnForceHiding = false;
- ArrayList<WindowStateAnimator> unForceHiding = null;
- WindowState wallpaper = null;
- final WallpaperController wallpaperController = mService.mWallpaperControllerLocked;
- for (int i = windows.size() - 1; i >= 0; i--) {
- WindowState win = windows.get(i);
- WindowStateAnimator winAnimator = win.mWinAnimator;
- final int flags = win.mAttrs.flags;
- boolean canBeForceHidden = mPolicy.canBeForceHidden(win, win.mAttrs);
- boolean shouldBeForceHidden = shouldForceHide(win);
- if (winAnimator.hasSurface()) {
- final boolean wasAnimating = winAnimator.mWasAnimating;
- final boolean nowAnimating = winAnimator.stepAnimationLocked(mCurrentTime);
- winAnimator.mWasAnimating = nowAnimating;
- orAnimating(nowAnimating);
-
- if (DEBUG_WALLPAPER) {
- Slog.v(TAG, win + ": wasAnimating=" + wasAnimating +
- ", nowAnimating=" + nowAnimating);
- }
-
- if (wasAnimating && !winAnimator.mAnimating
- && wallpaperController.isWallpaperTarget(win)) {
- mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
- setPendingLayoutChanges(Display.DEFAULT_DISPLAY,
- WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
- if (DEBUG_LAYOUT_REPEATS) {
- mWindowPlacerLocked.debugLayoutRepeats(
- "updateWindowsAndWallpaperLocked 2",
- getPendingLayoutChanges(Display.DEFAULT_DISPLAY));
- }
- }
-
- if (mPolicy.isForceHiding(win.mAttrs)) {
- if (!wasAnimating && nowAnimating) {
- if (DEBUG_KEYGUARD || DEBUG_ANIM ||
- DEBUG_VISIBILITY) Slog.v(TAG,
- "Animation started that could impact force hide: " + win);
- mBulkUpdateParams |= SET_FORCE_HIDING_CHANGED;
- setPendingLayoutChanges(displayId,
- WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
- if (DEBUG_LAYOUT_REPEATS) {
- mWindowPlacerLocked.debugLayoutRepeats(
- "updateWindowsAndWallpaperLocked 3",
- getPendingLayoutChanges(displayId));
- }
- mService.mFocusMayChange = true;
- } else if (mKeyguardGoingAway && !nowAnimating) {
- // Timeout!!
- Slog.e(TAG, "Timeout waiting for animation to startup");
- mPolicy.startKeyguardExitAnimation(0, 0);
- mKeyguardGoingAway = false;
- }
- if (win.isReadyForDisplay()) {
- if (nowAnimating && win.mWinAnimator.mKeyguardGoingAwayAnimation) {
- mForceHiding = KEYGUARD_ANIMATING_OUT;
- } else {
- mForceHiding = win.isDrawnLw() ? KEYGUARD_SHOWN : KEYGUARD_NOT_SHOWN;
- }
- }
- if (DEBUG_KEYGUARD || DEBUG_VISIBILITY) Slog.v(TAG,
- "Force hide " + forceHidingToString()
- + " hasSurface=" + win.mHasSurface
- + " policyVis=" + win.mPolicyVisibility
- + " destroying=" + win.mDestroying
- + " parentHidden=" + win.isParentWindowHidden()
- + " vis=" + win.mViewVisibility
- + " hidden=" + win.mToken.hidden
- + " anim=" + win.mWinAnimator.mAnimation);
- } else if (canBeForceHidden) {
- if (shouldBeForceHidden) {
- if (!win.hideLw(false, false)) {
- // Was already hidden
- continue;
- }
- if (DEBUG_KEYGUARD || DEBUG_VISIBILITY) Slog.v(TAG,
- "Now policy hidden: " + win);
- } else {
- boolean applyExistingExitAnimation = mPostKeyguardExitAnimation != null
- && !mPostKeyguardExitAnimation.hasEnded()
- && !winAnimator.mKeyguardGoingAwayAnimation
- && win.hasDrawnLw()
- && !win.isChildWindow()
- && !win.mIsImWindow
- && displayId == Display.DEFAULT_DISPLAY;
-
- // If the window is already showing and we don't need to apply an existing
- // Keyguard exit animation, skip.
- if (!win.showLw(false, false) && !applyExistingExitAnimation) {
- continue;
- }
- final boolean visibleNow = win.isVisibleNow();
- if (!visibleNow) {
- // Couldn't really show, must showLw() again when win becomes visible.
- win.hideLw(false, false);
- continue;
- }
- if (DEBUG_KEYGUARD || DEBUG_VISIBILITY) Slog.v(TAG,
- "Now policy shown: " + win);
- if ((mBulkUpdateParams & SET_FORCE_HIDING_CHANGED) != 0
- && !win.isChildWindow()) {
- if (unForceHiding == null) {
- unForceHiding = new ArrayList<>();
- }
- unForceHiding.add(winAnimator);
- if ((flags & FLAG_SHOW_WALLPAPER) != 0) {
- wallpaperInUnForceHiding = true;
- }
- if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
- startingInUnForceHiding = true;
- }
- } else if (applyExistingExitAnimation) {
- // We're already in the middle of an animation. Use the existing
- // animation to bring in this window.
- if (DEBUG_KEYGUARD) Slog.v(TAG,
- "Applying existing Keyguard exit animation to new window: win="
- + win);
-
- Animation a = mPolicy.createForceHideEnterAnimation(false,
- keyguardGoingAwayToShade);
- winAnimator.setAnimation(a, mPostKeyguardExitAnimation.getStartTime(),
- STACK_CLIP_BEFORE_ANIM);
- winAnimator.mKeyguardGoingAwayAnimation = true;
- winAnimator.mKeyguardGoingAwayWithWallpaper
- = keyguardGoingAwayWithWallpaper;
- }
- final WindowState currentFocus = mService.mCurrentFocus;
- if (currentFocus == null || currentFocus.mLayer < win.mLayer) {
- // We are showing on top of the current
- // focus, so re-evaluate focus to make
- // sure it is correct.
- if (DEBUG_FOCUS_LIGHT) Slog.v(TAG,
- "updateWindowsLocked: setting mFocusMayChange true");
- mService.mFocusMayChange = true;
- }
- }
- if ((flags & FLAG_SHOW_WALLPAPER) != 0) {
- mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
- setPendingLayoutChanges(Display.DEFAULT_DISPLAY,
- WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
- if (DEBUG_LAYOUT_REPEATS) {
- mWindowPlacerLocked.debugLayoutRepeats(
- "updateWindowsAndWallpaperLocked 4",
- getPendingLayoutChanges(Display.DEFAULT_DISPLAY));
- }
- }
- }
- }
-
- // If the window doesn't have a surface, the only thing we care about is the correct
- // policy visibility.
- else if (canBeForceHidden) {
- if (shouldBeForceHidden) {
- win.hideLw(false, false);
- } else {
- win.showLw(false, false);
- }
- }
-
- final AppWindowToken atoken = win.mAppToken;
- if (winAnimator.mDrawState == READY_TO_SHOW) {
- if (atoken == null || atoken.allDrawn) {
- if (win.performShowLocked()) {
- setPendingLayoutChanges(displayId,
- WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
- if (DEBUG_LAYOUT_REPEATS) {
- mWindowPlacerLocked.debugLayoutRepeats(
- "updateWindowsAndWallpaperLocked 5",
- getPendingLayoutChanges(displayId));
- }
- }
- }
- }
- final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
- if (appAnimator != null && appAnimator.thumbnail != null) {
- if (appAnimator.thumbnailTransactionSeq != mAnimTransactionSequence) {
- appAnimator.thumbnailTransactionSeq = mAnimTransactionSequence;
- appAnimator.thumbnailLayer = 0;
- }
- if (appAnimator.thumbnailLayer < winAnimator.mAnimLayer) {
- appAnimator.thumbnailLayer = winAnimator.mAnimLayer;
- }
- }
- if (win.mIsWallpaper) {
- wallpaper = win;
- }
- } // end forall windows
-
- // If we have windows that are being show due to them no longer
- // being force-hidden, apply the appropriate animation to them if animations are not
- // disabled.
- if (unForceHiding != null) {
- if (!keyguardGoingAwayNoAnimation) {
- boolean first = true;
- for (int i=unForceHiding.size()-1; i>=0; i--) {
- final WindowStateAnimator winAnimator = unForceHiding.get(i);
- Animation a = mPolicy.createForceHideEnterAnimation(
- wallpaperInUnForceHiding && !startingInUnForceHiding,
- keyguardGoingAwayToShade);
- if (a != null) {
- if (DEBUG_KEYGUARD) Slog.v(TAG,
- "Starting keyguard exit animation on window " + winAnimator.mWin);
- winAnimator.setAnimation(a, STACK_CLIP_BEFORE_ANIM);
- winAnimator.mKeyguardGoingAwayAnimation = true;
- winAnimator.mKeyguardGoingAwayWithWallpaper
- = keyguardGoingAwayWithWallpaper;
- if (first) {
- mPostKeyguardExitAnimation = a;
- mPostKeyguardExitAnimation.setStartTime(mCurrentTime);
- first = false;
- }
- }
- }
- } else if (mKeyguardGoingAway) {
- mPolicy.startKeyguardExitAnimation(mCurrentTime, 0 /* duration */);
- mKeyguardGoingAway = false;
- }
-
-
- // Wallpaper is going away in un-force-hide motion, animate it as well.
- if (!wallpaperInUnForceHiding && wallpaper != null && !keyguardGoingAwayNoAnimation) {
- if (DEBUG_KEYGUARD) Slog.d(TAG, "updateWindowsLocked: wallpaper animating away");
- Animation a = mPolicy.createForceHideWallpaperExitAnimation(
- keyguardGoingAwayToShade);
- if (a != null) {
- wallpaper.mWinAnimator.setAnimation(a);
- }
- }
- }
-
- if (mPostKeyguardExitAnimation != null) {
- // We're in the midst of a keyguard exit animation.
- if (mKeyguardGoingAway) {
- mPolicy.startKeyguardExitAnimation(mCurrentTime +
- mPostKeyguardExitAnimation.getStartOffset(),
- mPostKeyguardExitAnimation.getDuration());
- mKeyguardGoingAway = false;
- }
- // mPostKeyguardExitAnimation might either be ended normally, cancelled, or "orphaned",
- // meaning that the window it was running on was removed. We check for hasEnded() for
- // ended normally and cancelled case, and check the time for the "orphaned" case.
- else if (mPostKeyguardExitAnimation.hasEnded()
- || mCurrentTime - mPostKeyguardExitAnimation.getStartTime()
- > mPostKeyguardExitAnimation.getDuration()) {
- // Done with the animation, reset.
- if (DEBUG_KEYGUARD) Slog.v(TAG, "Done with Keyguard exit animations.");
- mPostKeyguardExitAnimation = null;
- }
- }
-
- final WindowState winShowWhenLocked = (WindowState) mPolicy.getWinShowWhenLockedLw();
- if (winShowWhenLocked != null) {
- mLastShowWinWhenLocked = winShowWhenLocked;
- }
- }
-
- private void updateWallpaperLocked(int displayId) {
- mService.mRoot.getDisplayContentOrCreate(displayId).resetAnimationBackgroundAnimator();
-
- final WindowList windows = mService.getWindowListLocked(displayId);
- WindowState detachedWallpaper = null;
-
- for (int i = windows.size() - 1; i >= 0; i--) {
- final WindowState win = windows.get(i);
- WindowStateAnimator winAnimator = win.mWinAnimator;
- if (winAnimator.mSurfaceController == null || !winAnimator.hasSurface()) {
- continue;
- }
-
- final int flags = win.mAttrs.flags;
-
- // If this window is animating, make a note that we have
- // an animating window and take care of a request to run
- // a detached wallpaper animation.
- if (winAnimator.mAnimating) {
- if (winAnimator.mAnimation != null) {
- if ((flags & FLAG_SHOW_WALLPAPER) != 0
- && winAnimator.mAnimation.getDetachWallpaper()) {
- detachedWallpaper = win;
- }
- final int color = winAnimator.mAnimation.getBackgroundColor();
- if (color != 0) {
- final TaskStack stack = win.getStack();
- if (stack != null) {
- stack.setAnimationBackground(winAnimator, color);
- }
- }
- }
- setAnimating(true);
- }
-
- // If this window's app token is running a detached wallpaper
- // animation, make a note so we can ensure the wallpaper is
- // displayed behind it.
- final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
- if (appAnimator != null && appAnimator.animation != null
- && appAnimator.animating) {
- if ((flags & FLAG_SHOW_WALLPAPER) != 0
- && appAnimator.animation.getDetachWallpaper()) {
- detachedWallpaper = win;
- }
-
- final int color = appAnimator.animation.getBackgroundColor();
- if (color != 0) {
- final TaskStack stack = win.getStack();
- if (stack != null) {
- stack.setAnimationBackground(winAnimator, color);
- }
- }
- }
- } // end forall windows
-
- if (mWindowDetachedWallpaper != detachedWallpaper) {
- if (DEBUG_WALLPAPER) Slog.v(TAG,
- "Detached wallpaper changed from " + mWindowDetachedWallpaper
- + " to " + detachedWallpaper);
- mWindowDetachedWallpaper = detachedWallpaper;
- mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
- }
- }
-
/** Locked on mService.mWindowMap. */
private void animateLocked(long frameTimeNs) {
if (!mInitialized) {
@@ -606,17 +225,17 @@
Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
}
- if (SHOW_TRANSACTIONS) Slog.i(
- TAG, ">>> OPEN TRANSACTION animateLocked");
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION animateLocked");
mService.openSurfaceTransaction();
SurfaceControl.setAnimationTransaction();
try {
+ final AccessibilityController accessibilityController =
+ mService.mAccessibilityController;
final int numDisplays = mDisplayContentsAnimators.size();
for (int i = 0; i < numDisplays; i++) {
final int displayId = mDisplayContentsAnimators.keyAt(i);
- final DisplayContent displayContent = mService.mRoot.getDisplayContentOrCreate(
- displayId);
- displayContent.stepAppWindowsAnimation(mCurrentTime);
+ final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
+ dc.stepAppWindowsAnimation(mCurrentTime);
DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
final ScreenRotationAnimation screenRotationAnimation =
@@ -630,11 +249,10 @@
displayAnimator.mScreenRotationAnimation = null;
//TODO (multidisplay): Accessibility supported only for the default display.
- if (mService.mAccessibilityController != null
- && displayId == Display.DEFAULT_DISPLAY) {
- // We just finished rotation animation which means we did not
- // anounce the rotation and waited for it to end, announce now.
- mService.mAccessibilityController.onRotationChangedLocked(
+ if (accessibilityController != null && dc.isDefaultDisplay) {
+ // We just finished rotation animation which means we did not announce
+ // the rotation and waited for it to end, announce now.
+ accessibilityController.onRotationChangedLocked(
mService.getDefaultDisplayContentLocked(), mService.mRotation);
}
}
@@ -642,22 +260,17 @@
// Update animations of all applications, including those
// associated with exiting/removed apps
- updateWindowsLocked(displayId);
- updateWallpaperLocked(displayId);
-
- final WindowList windows = mService.getWindowListLocked(displayId);
- final int N = windows.size();
- for (int j = 0; j < N; j++) {
- windows.get(j).mWinAnimator.prepareSurfaceLocked(true);
- }
+ ++mAnimTransactionSequence;
+ dc.updateWindowsForAnimator(this);
+ dc.updateWallpaperForAnimator(this);
+ dc.prepareWindowSurfaces();
}
for (int i = 0; i < numDisplays; i++) {
final int displayId = mDisplayContentsAnimators.keyAt(i);
- final DisplayContent displayContent = mService.mRoot.getDisplayContentOrCreate(
- displayId);
+ final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
- displayContent.checkAppWindowsReadyToShow();
+ dc.checkAppWindowsReadyToShow();
final ScreenRotationAnimation screenRotationAnimation =
mDisplayContentsAnimators.valueAt(i).mScreenRotationAnimation;
@@ -665,11 +278,11 @@
screenRotationAnimation.updateSurfacesInTransaction();
}
- orAnimating(displayContent.animateDimLayers());
- orAnimating(displayContent.getDockedDividerController().animate(mCurrentTime));
+ orAnimating(dc.animateDimLayers());
+ orAnimating(dc.getDockedDividerController().animate(mCurrentTime));
//TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null && displayContent.isDefaultDisplay) {
- mService.mAccessibilityController.drawMagnifiedRegionBorderIfNeededLocked();
+ if (accessibilityController != null && dc.isDefaultDisplay) {
+ accessibilityController.drawMagnifiedRegionBorderIfNeededLocked();
}
}
@@ -688,8 +301,7 @@
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
mService.closeSurfaceTransaction();
- if (SHOW_TRANSACTIONS) Slog.i(
- TAG, "<<< CLOSE TRANSACTION animateLocked");
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animateLocked");
}
boolean hasPendingLayoutChanges = mService.mRoot.hasPendingLayoutChanges(this);
@@ -726,7 +338,7 @@
Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating
+ " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
+ " mPendingLayoutChanges(DEFAULT_DISPLAY)="
- + Integer.toHexString(getPendingLayoutChanges(Display.DEFAULT_DISPLAY)));
+ + Integer.toHexString(getPendingLayoutChanges(DEFAULT_DISPLAY)));
}
}
@@ -758,15 +370,10 @@
pw.print(prefix); pw.print("DisplayContentsAnimator #");
pw.print(mDisplayContentsAnimators.keyAt(i));
pw.println(":");
- DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
- final WindowList windows =
- mService.getWindowListLocked(mDisplayContentsAnimators.keyAt(i));
- final int N = windows.size();
- for (int j = 0; j < N; j++) {
- WindowStateAnimator wanim = windows.get(j).mWinAnimator;
- pw.print(subPrefix); pw.print("Window #"); pw.print(j);
- pw.print(": "); pw.println(wanim);
- }
+ final DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
+ final DisplayContent dc =
+ mService.mRoot.getDisplayContentOrCreate(mDisplayContentsAnimators.keyAt(i));
+ dc.dumpWindowAnimators(pw, subPrefix);
if (displayAnimator.mScreenRotationAnimation != null) {
pw.print(subPrefix); pw.println("mScreenRotationAnimation:");
displayAnimator.mScreenRotationAnimation.printTo(subSubPrefix, pw);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 285c40b..db61c3e 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -71,10 +71,14 @@
final protected void setParent(WindowContainer parent) {
mParent = parent;
- // Update full configuration of this container and all its children.
- onConfigurationChanged(mParent != null ? mParent.mFullConfiguration : Configuration.EMPTY);
- // Update merged override configuration of this container and all its children.
- onMergedOverrideConfigurationChanged();
+ // Removing parent usually means that we've detached this entity to destroy it or to attach
+ // to another parent. In both cases we don't need to update the configuration now.
+ if (mParent != null) {
+ // Update full configuration of this container and all its children.
+ onConfigurationChanged(mParent.mFullConfiguration);
+ // Update merged override configuration of this container and all its children.
+ onMergedOverrideConfigurationChanged();
+ }
}
// Temp. holders for a chain of containers we are currently processing.
@@ -95,22 +99,25 @@
+ " is already a child of container=" + child.getParent().getName()
+ " can't add to container=" + getName());
}
- child.setParent(this);
- if (mChildren.isEmpty() || comparator == null) {
- mChildren.add(child);
- return;
- }
-
- final int count = mChildren.size();
- for (int i = 0; i < count; i++) {
- if (comparator.compare(child, mChildren.get(i)) < 0) {
- mChildren.add(i, child);
- return;
+ int positionToAdd = -1;
+ if (comparator != null) {
+ final int count = mChildren.size();
+ for (int i = 0; i < count; i++) {
+ if (comparator.compare(child, mChildren.get(i)) < 0) {
+ positionToAdd = i;
+ break;
+ }
}
}
- mChildren.add(child);
+ if (positionToAdd == -1) {
+ mChildren.add(child);
+ } else {
+ mChildren.add(positionToAdd, child);
+ }
+ // Set the parent after we've actually added a child in case a subclass depends on this.
+ child.setParent(this);
}
/** Adds the input window container has a child of this container at the input index. */
@@ -121,8 +128,9 @@
+ " is already a child of container=" + child.getParent().getName()
+ " can't add to container=" + getName());
}
- child.setParent(this);
mChildren.add(index, child);
+ // Set the parent after we've actually added a child in case a subclass depends on this.
+ child.setParent(this);
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowLayersController.java b/services/core/java/com/android/server/wm/WindowLayersController.java
index 68c2be9..e184e39 100644
--- a/services/core/java/com/android/server/wm/WindowLayersController.java
+++ b/services/core/java/com/android/server/wm/WindowLayersController.java
@@ -48,12 +48,10 @@
* <li>replaced windows, which need to live above their normal level, because they anticipate
* an animation</li>.
*/
-public class WindowLayersController {
+class WindowLayersController {
private final WindowManagerService mService;
- private int mInputMethodAnimLayerAdjustment;
-
- public WindowLayersController(WindowManagerService service) {
+ WindowLayersController(WindowManagerService service) {
mService = service;
}
@@ -65,7 +63,7 @@
private WindowState mDockDivider = null;
private ArrayDeque<WindowState> mReplacingWindows = new ArrayDeque<>();
- final void assignLayersLocked(WindowList windows) {
+ final void assignWindowLayers(WindowList windows) {
if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based on windows=" + windows,
new RuntimeException("here").fillInStackTrace());
@@ -87,7 +85,7 @@
// TODO: Preserved old behavior of code here but not sure comparing
// oldLayer to mAnimLayer and mLayer makes sense...though the
- // worst case would be unintentionalp layer reassignment.
+ // worst case would be unintentional layer reassignment.
if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) {
layerChanged = true;
anyLayerChanged = true;
@@ -115,41 +113,6 @@
if (DEBUG_LAYERS) logDebugLayers(windows);
}
- void setInputMethodAnimLayerAdjustment(int adj) {
- if (DEBUG_LAYERS) Slog.v(TAG_WM, "Setting im layer adj to " + adj);
- mInputMethodAnimLayerAdjustment = adj;
- final WindowState imw = mService.mInputMethodWindow;
- if (imw != null) {
- imw.adjustAnimLayer(adj);
- }
- for (int i = mService.mInputMethodDialogs.size() - 1; i >= 0; i--) {
- final WindowState dialog = mService.mInputMethodDialogs.get(i);
- // TODO: This and other places setting mAnimLayer can probably use WS.adjustAnimLayer,
- // but need to make sure we are not setting things twice for child windows that are
- // already in the list.
- dialog.mWinAnimator.mAnimLayer = dialog.mLayer + adj;
- if (DEBUG_LAYERS) Slog.v(TAG_WM, "IM win " + imw
- + " anim layer: " + dialog.mWinAnimator.mAnimLayer);
- }
- }
-
- int getSpecialWindowAnimLayerAdjustment(WindowState win) {
- if (win.mIsImWindow) {
- return mInputMethodAnimLayerAdjustment;
- } else if (win.mIsWallpaper) {
- return mService.mWallpaperControllerLocked.getAnimLayerAdjustment();
- }
- return 0;
- }
-
- /**
- * @return The layer used for dimming the apps when dismissing docked/fullscreen stack. Just
- * above all application surfaces.
- */
- int getResizeDimLayer() {
- return (mDockDivider != null) ? mDockDivider.mLayer - 1 : LAYER_OFFSET_DIM;
- }
-
private void logDebugLayers(WindowList windows) {
for (int i = 0, n = windows.size(); i < n; i++) {
final WindowState w = windows.get(i);
@@ -250,21 +213,11 @@
private void assignAnimLayer(WindowState w, int layer) {
w.mLayer = layer;
- w.mWinAnimator.mAnimLayer = w.mLayer + w.getAnimLayerAdjustment() +
- getSpecialWindowAnimLayerAdjustment(w);
+ w.mWinAnimator.mAnimLayer = w.getAnimLayerAdjustment()
+ + w.getSpecialWindowAnimLayerAdjustment();
if (w.mAppToken != null && w.mAppToken.mAppAnimator.thumbnailForceAboveLayer > 0
&& w.mWinAnimator.mAnimLayer > w.mAppToken.mAppAnimator.thumbnailForceAboveLayer) {
w.mAppToken.mAppAnimator.thumbnailForceAboveLayer = w.mWinAnimator.mAnimLayer;
}
}
-
- void dump(PrintWriter pw, String s) {
- if (mInputMethodAnimLayerAdjustment != 0 ||
- mService.mWallpaperControllerLocked.getAnimLayerAdjustment() != 0) {
- pw.print(" mInputMethodAnimLayerAdjustment=");
- pw.print(mInputMethodAnimLayerAdjustment);
- pw.print(" mWallpaperAnimLayerAdjustment=");
- pw.println(mService.mWallpaperControllerLocked.getAnimLayerAdjustment());
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 02d04cc..e6c9512 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -172,6 +172,7 @@
import static android.app.StatusBarManager.DISABLE_MASK;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
@@ -189,6 +190,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAG;
import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -514,9 +516,9 @@
// The root of the device window hierarchy.
RootWindowContainer mRoot;
- // TODO: Move several of this states to the RootWindowContainer
+ // TODO: Move several of this states to the RootWindowContainer or DisplayContent
int mRotation = 0;
- int mLastOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ int mLastOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
boolean mAltOrientation = false;
private boolean mKeyguardWaitingForActivityDrawn;
@@ -529,8 +531,7 @@
boolean mForceResizableTasks = false;
int getDragLayerLocked() {
- return mPolicy.windowTypeToLayerLw(LayoutParams.TYPE_DRAG) * TYPE_LAYER_MULTIPLIER
- + TYPE_LAYER_OFFSET;
+ return mPolicy.windowTypeToLayerLw(TYPE_DRAG) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
}
class RotationWatcher {
@@ -561,8 +562,8 @@
boolean mClientFreezingScreen = false;
int mAppsFreezingScreen = 0;
- int mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
- int mLastKeyguardForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ int mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+ int mLastKeyguardForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
int mLayoutSeq = 0;
@@ -607,6 +608,16 @@
boolean mInputMethodTargetWaitingAnim;
WindowState mInputMethodWindow = null;
+ // TODO: Remove with extreme prejudice! This list is maintained so that we can keep track of
+ // dialogs that should go on top of the IME so they can be re-arranged in the window list any
+ // time the IME changes position in the window list. Normally you would have a dialog be a child
+ // window of the IME, but they don't share the same token since they are added by different
+ // clients. This doesn't really affect what the user sees on screen since this dialogs have an
+ // higher base layer than the IME window, but it will affect users of the window list that
+ // expect the list to represent the order of things on-screen (e.g input service). This makes
+ // the code for managing the window list hard to follow (see all the places it is used).
+ // We can remove the use of this field when we automatically assign layers and z-order the
+ // window list before it is used whenever window container order changes.
final ArrayList<WindowState> mInputMethodDialogs = new ArrayList<>();
/** Temporary list for comparison. Always clear this after use so we don't end up with
@@ -673,10 +684,6 @@
}
}
- WallpaperController mWallpaperControllerLocked;
-
- final WindowLayersController mLayersController;
-
boolean mAnimateWallpaperWithTarget;
// TODO: Move to RootWindowContainer
@@ -685,19 +692,19 @@
PowerManager mPowerManager;
PowerManagerInternal mPowerManagerInternal;
- float mWindowAnimationScaleSetting = 1.0f;
- float mTransitionAnimationScaleSetting = 1.0f;
- float mAnimatorDurationScaleSetting = 1.0f;
- boolean mAnimationsDisabled = false;
+ private float mWindowAnimationScaleSetting = 1.0f;
+ private float mTransitionAnimationScaleSetting = 1.0f;
+ private float mAnimatorDurationScaleSetting = 1.0f;
+ private boolean mAnimationsDisabled = false;
final InputManagerService mInputManager;
final DisplayManagerInternal mDisplayManagerInternal;
final DisplayManager mDisplayManager;
- final Display[] mDisplays;
+ private final Display[] mDisplays;
// Who is holding the screen on.
- Session mHoldingScreenOn;
- PowerManager.WakeLock mHoldingScreenWakeLock;
+ private Session mHoldingScreenOn;
+ private PowerManager.WakeLock mHoldingScreenWakeLock;
boolean mTurnOnScreen;
@@ -708,7 +715,7 @@
DragState mDragState = null;
// For frozen screen animations.
- int mExitAnimId, mEnterAnimId;
+ private int mExitAnimId, mEnterAnimId;
boolean mAnimationScheduled;
@@ -970,15 +977,18 @@
mDisplaySettings = new DisplaySettings();
mDisplaySettings.readSettingsLocked();
- mWallpaperControllerLocked = new WallpaperController(this);
mWindowPlacerLocked = new WindowSurfacePlacer(this);
- mLayersController = new WindowLayersController(this);
mPolicy = policy;
LocalServices.addService(WindowManagerPolicy.class, mPolicy);
- mPointerEventDispatcher = mInputManager != null
- ? new PointerEventDispatcher(mInputManager.monitorInput(TAG_WM)) : null;
+ if(mInputManager != null) {
+ final InputChannel inputChannel = mInputManager.monitorInput(TAG_WM);
+ mPointerEventDispatcher = inputChannel != null
+ ? new PointerEventDispatcher(inputChannel) : null;
+ } else {
+ mPointerEventDispatcher = null;
+ }
mFxSession = new SurfaceSession();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
@@ -1085,353 +1095,6 @@
}
}
- static boolean canBeImeTarget(WindowState w) {
- final int fl = w.mAttrs.flags
- & (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM);
- final int type = w.mAttrs.type;
- if (fl == 0 || fl == (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM)
- || type == TYPE_APPLICATION_STARTING) {
- if (DEBUG_INPUT_METHOD) {
- Slog.i(TAG_WM, "isVisibleOrAdding " + w + ": " + w.isVisibleOrAdding());
- if (!w.isVisibleOrAdding()) {
- Slog.i(TAG_WM, " mSurfaceController=" + w.mWinAnimator.mSurfaceController
- + " relayoutCalled=" + w.mRelayoutCalled
- + " viewVis=" + w.mViewVisibility
- + " policyVis=" + w.mPolicyVisibility
- + " policyVisAfterAnim=" + w.mPolicyVisibilityAfterAnim
- + " parentHidden=" + w.isParentWindowHidden()
- + " exiting=" + w.mAnimatingExit + " destroying=" + w.mDestroying);
- if (w.mAppToken != null) {
- Slog.i(TAG_WM, " mAppToken.hiddenRequested=" + w.mAppToken.hiddenRequested);
- }
- }
- }
- return w.isVisibleOrAdding();
- }
- return false;
- }
-
- /**
- * Dig through the WindowStates and find the one that the Input Method will target.
- * @param willMove
- * @return The index+1 in mWindows of the discovered target.
- */
- int findDesiredInputMethodWindowIndexLocked(boolean willMove) {
- // TODO(multidisplay): Needs some serious rethought when the target and IME are not on the
- // same display. Or even when the current IME/target are not on the same screen as the next
- // IME/target. For now only look for input windows on the main screen.
- WindowList windows = getDefaultWindowListLocked();
- WindowState w = null;
- int i;
- for (i = windows.size() - 1; i >= 0; --i) {
- WindowState win = windows.get(i);
-
- if (DEBUG_INPUT_METHOD && willMove) Slog.i(TAG_WM, "Checking window @" + i
- + " " + win + " fl=0x" + Integer.toHexString(win.mAttrs.flags));
- if (canBeImeTarget(win)) {
- w = win;
- //Slog.i(TAG_WM, "Putting input method here!");
-
- // Yet more tricksyness! If this window is a "starting"
- // window, we do actually want to be on top of it, but
- // it is not -really- where input will go. So if the caller
- // is not actually looking to move the IME, look down below
- // for a real window to target...
- if (!willMove
- && w.mAttrs.type == TYPE_APPLICATION_STARTING
- && i > 0) {
- WindowState wb = windows.get(i-1);
- if (wb.mAppToken == w.mAppToken && canBeImeTarget(wb)) {
- i--;
- w = wb;
- }
- }
- break;
- }
- }
-
- // Now w is either mWindows[0] or an IME (or null if mWindows is empty).
-
- if (DEBUG_INPUT_METHOD && willMove) Slog.v(TAG_WM, "Proposed new IME target: " + w);
-
- // Now, a special case -- if the last target's window is in the
- // process of exiting, and is above the new target, keep on the
- // last target to avoid flicker. Consider for example a Dialog with
- // the IME shown: when the Dialog is dismissed, we want to keep
- // the IME above it until it is completely gone so it doesn't drop
- // behind the dialog or its full-screen scrim.
- final WindowState curTarget = mInputMethodTarget;
- if (curTarget != null
- && curTarget.isDisplayedLw()
- && curTarget.isClosing()
- && (w == null || curTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer)) {
- if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Current target higher, not changing");
- return windows.indexOf(curTarget) + 1;
- }
-
- if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Desired input method target="
- + w + " willMove=" + willMove);
-
- if (willMove && w != null) {
- AppWindowToken token = curTarget == null ? null : curTarget.mAppToken;
- if (token != null) {
-
- // Now some fun for dealing with window animations that
- // modify the Z order. We need to look at all windows below
- // the current target that are in this app, finding the highest
- // visible one in layering.
- WindowState highestTarget = null;
- int highestPos = 0;
- if (token.mAppAnimator.animating || token.mAppAnimator.animation != null) {
- WindowList curWindows = token.getDisplayContent().getWindowList();
- int pos = curWindows.indexOf(curTarget);
- while (pos >= 0) {
- WindowState win = curWindows.get(pos);
- if (win.mAppToken != token) {
- break;
- }
- if (!win.mRemoved) {
- if (highestTarget == null || win.mWinAnimator.mAnimLayer >
- highestTarget.mWinAnimator.mAnimLayer) {
- highestTarget = win;
- highestPos = pos;
- }
- }
- pos--;
- }
- }
-
- if (highestTarget != null) {
- if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, mAppTransition + " " + highestTarget
- + " animating=" + highestTarget.mWinAnimator.isAnimationSet()
- + " layer=" + highestTarget.mWinAnimator.mAnimLayer
- + " new layer=" + w.mWinAnimator.mAnimLayer);
-
- if (mAppTransition.isTransitionSet()) {
- // If we are currently setting up for an animation,
- // hold everything until we can find out what will happen.
- mInputMethodTargetWaitingAnim = true;
- mInputMethodTarget = highestTarget;
- return highestPos + 1;
- } else if (highestTarget.mWinAnimator.isAnimationSet() &&
- highestTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer) {
- // If the window we are currently targeting is involved
- // with an animation, and it is on top of the next target
- // we will be over, then hold off on moving until
- // that is done.
- mInputMethodTargetWaitingAnim = true;
- mInputMethodTarget = highestTarget;
- return highestPos + 1;
- }
- }
- }
- }
-
- //Slog.i(TAG_WM, "Placing input method @" + (i+1));
- if (w != null) {
- if (willMove) {
- if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget + " to "
- + w + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : ""));
- mInputMethodTarget = w;
- mInputMethodTargetWaitingAnim = false;
- if (w.mAppToken != null) {
- mLayersController.setInputMethodAnimLayerAdjustment(
- w.mAppToken.mAppAnimator.animLayerAdjustment);
- } else {
- mLayersController.setInputMethodAnimLayerAdjustment(0);
- }
- }
-
- // If the docked divider is visible, we still need to go through this whole
- // excercise to find the appropriate input method target (used for animations
- // and dialog adjustments), but for purposes of Z ordering we simply wish to
- // place it above the docked divider. Unless it is already above the divider.
- final WindowState dockedDivider =
- w.getDisplayContent().mDividerControllerLocked.getWindow();
- if (dockedDivider != null && dockedDivider.isVisibleLw()) {
- int dividerIndex = windows.indexOf(dockedDivider);
- if (dividerIndex > 0 && dividerIndex > i) {
- return dividerIndex + 1;
- }
- }
- return i+1;
- }
- if (willMove) {
- if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget + " to null."
- + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : ""));
- mInputMethodTarget = null;
- mLayersController.setInputMethodAnimLayerAdjustment(0);
- }
- return -1;
- }
-
- private void reAddWindowToListInOrderLocked(WindowState win) {
- win.mToken.addWindow(win);
- // This is a hack to get all of the child windows added as well at the right position. Child
- // windows should be rare and this case should be rare, so it shouldn't be that big a deal.
- WindowList windows = win.getWindowList();
- int wpos = windows.indexOf(win);
- if (wpos >= 0) {
- if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "ReAdd removing from " + wpos + ": " + win);
- windows.remove(wpos);
- mWindowsChanged = true;
- win.reAddWindow(wpos);
- }
- }
-
- private void logWindowList(final WindowList windows, String prefix) {
- int N = windows.size();
- while (N > 0) {
- N--;
- Slog.v(TAG_WM, prefix + "#" + N + ": " + windows.get(N));
- }
- }
-
- void moveInputMethodDialogsLocked(int pos) {
- ArrayList<WindowState> dialogs = mInputMethodDialogs;
-
- // TODO(multidisplay): IMEs are only supported on the default display.
- WindowList windows = getDefaultWindowListLocked();
- final int N = dialogs.size();
- if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Removing " + N + " dialogs w/pos=" + pos);
- for (int i=0; i<N; i++) {
- pos = dialogs.get(i).removeFromWindowList(pos);
- }
- if (DEBUG_INPUT_METHOD) {
- Slog.v(TAG_WM, "Window list w/pos=" + pos);
- logWindowList(windows, " ");
- }
-
- if (pos >= 0) {
- // Skip windows owned by the input method.
- if (mInputMethodWindow != null) {
- while (pos < windows.size()) {
- WindowState wp = windows.get(pos);
- if (wp == mInputMethodWindow || wp.getParentWindow() == mInputMethodWindow) {
- pos++;
- continue;
- }
- break;
- }
- }
- if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Adding " + N + " dialogs at pos=" + pos);
- for (int i=0; i<N; i++) {
- WindowState win = dialogs.get(i);
- pos = win.reAddWindow(pos);
- }
- if (DEBUG_INPUT_METHOD) {
- Slog.v(TAG_WM, "Final window list:");
- logWindowList(windows, " ");
- }
- return;
- }
- for (int i=0; i<N; i++) {
- WindowState win = dialogs.get(i);
- reAddWindowToListInOrderLocked(win);
- if (DEBUG_INPUT_METHOD) {
- Slog.v(TAG_WM, "No IM target, final list:");
- logWindowList(windows, " ");
- }
- }
- }
-
- boolean moveInputMethodWindowsIfNeededLocked(boolean needAssignLayers) {
- final WindowState imWin = mInputMethodWindow;
- final int DN = mInputMethodDialogs.size();
- if (imWin == null && DN == 0) {
- return false;
- }
-
- // TODO(multidisplay): IMEs are only supported on the default display.
- WindowList windows = getDefaultWindowListLocked();
-
- int imPos = findDesiredInputMethodWindowIndexLocked(true);
- if (imPos >= 0) {
- // In this case, the input method windows are to be placed
- // immediately above the window they are targeting.
-
- // First check to see if the input method windows are already
- // located here, and contiguous.
- final int N = windows.size();
- final WindowState firstImWin = imPos < N ? windows.get(imPos) : null;
-
- // Figure out the actual input method window that should be
- // at the bottom of their stack.
- WindowState baseImWin = imWin != null ? imWin : mInputMethodDialogs.get(0);
- final WindowState cw = baseImWin.getBottomChild();
- if (cw != null && cw.mSubLayer < 0) {
- baseImWin = cw;
- }
-
- if (firstImWin == baseImWin) {
- // The windows haven't moved... but are they still contiguous?
- // First find the top IM window.
- int pos = imPos+1;
- while (pos < N) {
- if (!(windows.get(pos)).mIsImWindow) {
- break;
- }
- pos++;
- }
- pos++;
- // Now there should be no more input method windows above.
- while (pos < N) {
- if ((windows.get(pos)).mIsImWindow) {
- break;
- }
- pos++;
- }
- if (pos >= N) {
- return false;
- }
- }
-
- if (imWin != null) {
- if (DEBUG_INPUT_METHOD) {
- Slog.v(TAG_WM, "Moving IM from " + imPos);
- logWindowList(windows, " ");
- }
- imPos = imWin.removeFromWindowList(imPos);
- if (DEBUG_INPUT_METHOD) {
- Slog.v(TAG_WM, "List after removing with new pos " + imPos + ":");
- logWindowList(windows, " ");
- }
- imWin.reAddWindow(imPos);
- if (DEBUG_INPUT_METHOD) {
- Slog.v(TAG_WM, "List after moving IM to " + imPos + ":");
- logWindowList(windows, " ");
- }
- if (DN > 0) moveInputMethodDialogsLocked(imPos+1);
- } else {
- moveInputMethodDialogsLocked(imPos);
- }
-
- } else {
- // In this case, the input method windows go in a fixed layer,
- // because they aren't currently associated with a focus window.
-
- if (imWin != null) {
- if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Moving IM from " + imPos);
- imWin.removeFromWindowList(0);
- reAddWindowToListInOrderLocked(imWin);
- if (DEBUG_INPUT_METHOD) {
- Slog.v(TAG_WM, "List with no IM target:");
- logWindowList(windows, " ");
- }
- if (DN > 0) moveInputMethodDialogsLocked(-1);
- } else {
- moveInputMethodDialogsLocked(-1);
- }
-
- }
-
- if (needAssignLayers) {
- mLayersController.assignLayersLocked(windows);
- }
-
- return true;
- }
-
static boolean excludeWindowTypeFromTapOutTask(int windowType) {
switch (windowType) {
case TYPE_STATUS_BAR:
@@ -1725,16 +1388,17 @@
} else if (type == TYPE_INPUT_METHOD_DIALOG) {
mInputMethodDialogs.add(win);
win.mToken.addWindow(win);
- moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true));
+ displayContent.moveInputMethodDialogs(
+ displayContent.findDesiredInputMethodWindowIndex(true));
imMayMove = false;
} else {
win.mToken.addWindow(win);
if (type == TYPE_WALLPAPER) {
- mWallpaperControllerLocked.clearLastWallpaperTimeoutTime();
+ displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
} else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- } else if (mWallpaperControllerLocked.isBelowWallpaperTarget(win)) {
+ } else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {
// If there is currently a wallpaper being shown, and
// the base layer of the new window is below the current
// layer of the target window, then adjust the wallpaper.
@@ -1749,7 +1413,7 @@
win.applyAdjustForImeIfNeeded();
if (type == TYPE_DOCK_DIVIDER) {
- getDefaultDisplayContentLocked().getDockedDividerController().setWindow(win);
+ mRoot.getDisplayContent(displayId).getDockedDividerController().setWindow(win);
}
final WindowStateAnimator winAnimator = win.mWinAnimator;
@@ -1801,12 +1465,12 @@
}
if (imMayMove) {
- moveInputMethodWindowsIfNeededLocked(false);
+ displayContent.moveInputMethodWindowsIfNeeded(false);
}
- mLayersController.assignLayersLocked(displayContent.getWindowList());
// Don't do layout here, the window must call
// relayout to be displayed, so we'll do it there.
+ displayContent.assignWindowLayers(false /* setLayoutNeeded */);
if (focusChanged) {
mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
@@ -1816,13 +1480,13 @@
if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addWindow: New client "
+ client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5));
- if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) {
+ if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false, displayId)) {
reportNewConfig = true;
}
}
if (reportNewConfig) {
- sendNewConfiguration();
+ sendNewConfiguration(displayId);
}
Binder.restoreCallingIdentity(origId);
@@ -2008,19 +1672,17 @@
atoken.postWindowRemoveStartingWindowCleanup(win);
}
+ final DisplayContent dc = win.getDisplayContent();
if (win.mAttrs.type == TYPE_WALLPAPER) {
- mWallpaperControllerLocked.clearLastWallpaperTimeoutTime();
- getDefaultDisplayContentLocked().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ dc.mWallpaperController.clearLastWallpaperTimeoutTime();
+ dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
} else if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
- getDefaultDisplayContentLocked().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
- final WindowList windows = win.getWindowList();
- if (windows != null) {
- windows.remove(win);
+ if (dc != null && dc.removeFromWindowList(win)) {
if (!mWindowPlacerLocked.isInLayout()) {
- mLayersController.assignLayersLocked(windows);
- win.setDisplayLayoutNeeded();
+ dc.assignWindowLayers(true /* setLayoutNeeded */);
mWindowPlacerLocked.performSurfacePlacement();
if (win.mAppToken != null) {
win.mAppToken.updateReportedVisibilityLocked();
@@ -2031,7 +1693,7 @@
mInputMonitor.updateInputWindowsLw(true /*force*/);
}
- public void updateAppOpsState() {
+ private void updateAppOpsState() {
synchronized(mWindowMap) {
mRoot.updateAppOpsState();
}
@@ -2125,7 +1787,7 @@
if (mAccessibilityController != null) {
WindowState window = mWindowMap.get(token);
//TODO (multidisplay): Magnification is supported only for the default display.
- if (window != null && window.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ if (window != null && window.getDisplayId() == DEFAULT_DISPLAY) {
mAccessibilityController.onRectangleOnScreenRequestedLocked(rectangle);
}
}
@@ -2219,11 +1881,13 @@
== PackageManager.PERMISSION_GRANTED;
long origId = Binder.clearCallingIdentity();
+ final int displayId;
synchronized(mWindowMap) {
WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
return 0;
}
+ displayId = win.getDisplayId();
WindowStateAnimator winAnimator = win.mWinAnimator;
if (viewVisibility != View.GONE) {
@@ -2392,27 +2056,29 @@
// updateFocusedWindowLocked() already assigned layers so we only need to
// reassign them at this point if the IM window state gets shuffled
boolean toBeDisplayed = (result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0;
- if (imMayMove && (moveInputMethodWindowsIfNeededLocked(false) || toBeDisplayed)) {
- // Little hack here -- we -should- be able to rely on the
- // function to return true if the IME has moved and needs
- // its layer recomputed. However, if the IME was hidden
- // and isn't actually moved in the list, its layer may be
- // out of data so we make sure to recompute it.
- mLayersController.assignLayersLocked(win.getWindowList());
+ final DisplayContent dc = win.getDisplayContent();
+ if (imMayMove && (dc.moveInputMethodWindowsIfNeeded(false) || toBeDisplayed)) {
+ // Little hack here -- we -should- be able to rely on the function to return true if
+ // the IME has moved and needs its layer recomputed. However, if the IME was hidden
+ // and isn't actually moved in the list, its layer may be out of data so we make
+ // sure to recompute it.
+ // TODO: Probably not needed once the window list always has the right z-ordering
+ // when the window hierarchy is updated.
+ dc.assignWindowLayers(false /* setLayoutNeeded */);
}
if (wallpaperMayMove) {
- getDefaultDisplayContentLocked().pendingLayoutChanges |=
+ win.getDisplayContent().pendingLayoutChanges |=
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
}
win.setDisplayLayoutNeeded();
win.mGivenInsetsPending = (flags&WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
- configChanged = updateOrientationFromAppTokensLocked(false);
+ configChanged = updateOrientationFromAppTokensLocked(false, displayId);
mWindowPlacerLocked.performSurfacePlacement();
if (toBeDisplayed && win.mIsWallpaper) {
- DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
- mWallpaperControllerLocked.updateWallpaperOffset(
+ DisplayInfo displayInfo = win.getDisplayContent().getDisplayInfo();
+ dc.mWallpaperController.updateWallpaperOffset(
win, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
}
if (win.mAppToken != null) {
@@ -2457,7 +2123,7 @@
}
if (configChanged) {
- sendNewConfiguration();
+ sendNewConfiguration(displayId);
}
Binder.restoreCallingIdentity(origId);
return result;
@@ -2480,7 +2146,7 @@
// an exit.
win.mAnimatingExit = true;
win.mWinAnimator.mAnimating = true;
- } else if (mWallpaperControllerLocked.isWallpaperTarget(win)) {
+ } else if (win.getDisplayContent().mWallpaperController.isWallpaperTarget(win)) {
// If the wallpaper is currently behind this
// window, we need to change both of them inside
// of a transaction to avoid artifacts.
@@ -2492,9 +2158,8 @@
}
win.destroyOrSaveSurface();
}
- //TODO (multidisplay): Magnification is supported only for the default
- if (mAccessibilityController != null
- && win.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ // TODO(multidisplay): Magnification is supported only for the default display.
+ if (mAccessibilityController != null && win.getDisplayId() == DEFAULT_DISPLAY) {
mAccessibilityController.onWindowTransitionLocked(win, transit);
}
return focusMayChange;
@@ -2614,7 +2279,7 @@
}
}
- public void finishDrawingWindow(Session session, IWindow client) {
+ void finishDrawingWindow(Session session, IWindow client) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mWindowMap) {
@@ -2623,7 +2288,7 @@
+ (win != null ? win.mWinAnimator.drawStateToString() : "null"));
if (win != null && win.mWinAnimator.finishDrawingLocked()) {
if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
- getDefaultDisplayContentLocked().pendingLayoutChanges |=
+ win.getDisplayContent().pendingLayoutChanges |=
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
}
win.setDisplayLayoutNeeded();
@@ -2751,7 +2416,7 @@
final WindowToken wtoken = removedTokens.get(i);
wtoken.setExiting();
if (wtoken.windowType == TYPE_WALLPAPER) {
- mWallpaperControllerLocked.removeWallpaperToken(wtoken);
+ wtoken.getDisplayContent().mWallpaperController.removeWallpaperToken(wtoken);
}
mInputMonitor.updateInputWindowsLw(true /*force*/);
@@ -2865,107 +2530,35 @@
}
}
- public int getOrientationLocked() {
- if (mDisplayFrozen) {
- if (mLastWindowForcedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
- "Display is frozen, return " + mLastWindowForcedOrientation);
- // If the display is frozen, some activities may be in the middle
- // of restarting, and thus have removed their old window. If the
- // window has the flag to hide the lock screen, then the lock screen
- // can re-appear and inflict its own orientation on us. Keep the
- // orientation stable until this all settles down.
- return mLastWindowForcedOrientation;
- } else if (mPolicy.isKeyguardLocked()) {
- // Use the last orientation the while the display is frozen with the
- // keyguard locked. This could be the keyguard forced orientation or
- // from a SHOW_WHEN_LOCKED window. We don't want to check the show when
- // locked window directly though as things aren't stable while
- // the display is frozen, for example the window could be momentarily unavailable
- // due to activity relaunch.
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display is frozen while keyguard locked, "
- + "return " + mLastOrientation);
- return mLastOrientation;
- }
- } else {
- // TODO(multidisplay): Change to the correct display.
- final WindowList windows = getDefaultWindowListLocked();
- for (int pos = windows.size() - 1; pos >= 0; --pos) {
- WindowState win = windows.get(pos);
- if (win.mAppToken != null) {
- // We hit an application window. so the orientation will be determined by the
- // app window. No point in continuing further.
- break;
- }
- if (!win.isVisibleLw() || !win.mPolicyVisibilityAfterAnim) {
- continue;
- }
- int req = win.mAttrs.screenOrientation;
- if(req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND) {
- continue;
- }
-
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM, win + " forcing orientation to " + req);
- if (mPolicy.isKeyguardHostWindow(win.mAttrs)) {
- mLastKeyguardForcedOrientation = req;
- }
- return (mLastWindowForcedOrientation = req);
- }
- mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
-
- if (mPolicy.isKeyguardLocked()) {
- // The screen is locked and no top system window is requesting an orientation.
- // Return either the orientation of the show-when-locked app (if there is any) or
- // the orientation of the keyguard. No point in searching from the rest of apps.
- WindowState winShowWhenLocked = (WindowState) mPolicy.getWinShowWhenLockedLw();
- AppWindowToken appShowWhenLocked = winShowWhenLocked == null ?
- null : winShowWhenLocked.mAppToken;
- if (appShowWhenLocked != null) {
- int req = appShowWhenLocked.getOrientation();
- if (req == SCREEN_ORIENTATION_BEHIND) {
- req = mLastKeyguardForcedOrientation;
- }
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Done at " + appShowWhenLocked
- + " -- show when locked, return " + req);
- return req;
- }
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
- "No one is requesting an orientation when the screen is locked");
- return mLastKeyguardForcedOrientation;
- }
- }
-
- // Top system windows are not requesting an orientation. Start searching from apps.
- return getDefaultDisplayContentLocked().getOrientation();
- }
-
@Override
- public Configuration updateOrientationFromAppTokens(
- Configuration currentConfig, IBinder freezeThisOneIfNeeded) {
+ public Configuration updateOrientationFromAppTokens(Configuration currentConfig,
+ IBinder freezeThisOneIfNeeded, int displayId) {
if (!checkCallingPermission(MANAGE_APP_TOKENS, "updateOrientationFromAppTokens()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
- Configuration config = null;
- long ident = Binder.clearCallingIdentity();
-
- synchronized(mWindowMap) {
- config = updateOrientationFromAppTokensLocked(currentConfig,
- freezeThisOneIfNeeded);
+ final Configuration config;
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized(mWindowMap) {
+ config = updateOrientationFromAppTokensLocked(currentConfig, freezeThisOneIfNeeded,
+ displayId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
- Binder.restoreCallingIdentity(ident);
return config;
}
- private Configuration updateOrientationFromAppTokensLocked(
- Configuration currentConfig, IBinder freezeThisOneIfNeeded) {
+ private Configuration updateOrientationFromAppTokensLocked(Configuration currentConfig,
+ IBinder freezeThisOneIfNeeded, int displayId) {
if (!mDisplayReady) {
return null;
}
Configuration config = null;
- if (updateOrientationFromAppTokensLocked(false)) {
+ if (updateOrientationFromAppTokensLocked(false, displayId)) {
// If we changed the orientation but mOrientationChangeComplete is already true,
// we used seamless rotation, and we don't need to freeze the screen.
if (freezeThisOneIfNeeded != null && !mRoot.mOrientationChangeComplete) {
@@ -2974,7 +2567,7 @@
atoken.startFreezingScreen();
}
}
- config = computeNewConfigurationLocked();
+ config = computeNewConfigurationLocked(displayId);
} else if (currentConfig != null) {
// No obvious action we need to take, but if our current state mismatches the activity
@@ -2984,10 +2577,10 @@
// to keep override configs clear of non-empty values (e.g. fontSize).
mTempConfiguration.unset();
mTempConfiguration.updateFrom(currentConfig);
- computeScreenConfigurationLocked(mTempConfiguration);
+ computeScreenConfigurationLocked(mTempConfiguration, displayId);
if (currentConfig.diff(mTempConfiguration) != 0) {
mWaitingForConfig = true;
- final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
displayContent.setLayoutNeeded();
int anim[] = new int[2];
if (displayContent.isDimming()) {
@@ -3003,30 +2596,28 @@
return config;
}
- /*
- * Determine the new desired orientation of the display, returning
- * a non-null new Configuration if it has changed from the current
- * orientation. IF TRUE IS RETURNED SOMEONE MUST CALL
- * setNewConfiguration() TO TELL THE WINDOW MANAGER IT CAN UNFREEZE THE
- * SCREEN. This will typically be done for you if you call
- * sendNewConfiguration().
+ /**
+ * Determine the new desired orientation of the display, returning a non-null new Configuration
+ * if it has changed from the current orientation. IF TRUE IS RETURNED SOMEONE MUST CALL
+ * {@link #setNewDisplayOverrideConfiguration(Configuration, int)} TO TELL THE WINDOW MANAGER IT
+ * CAN UNFREEZE THE SCREEN. This will typically be done for you if you call
+ * {@link #sendNewConfiguration(int)}.
*
- * The orientation is computed from non-application windows first. If none of
- * the non-application windows specify orientation, the orientation is computed from
- * application tokens.
- * @see android.view.IWindowManager#updateOrientationFromAppTokens(
- * android.os.IBinder)
+ * The orientation is computed from non-application windows first. If none of the
+ * non-application windows specify orientation, the orientation is computed from application
+ * tokens.
+ * @see android.view.IWindowManager#updateOrientationFromAppTokens(Configuration, IBinder, int)
*/
- boolean updateOrientationFromAppTokensLocked(boolean inTransaction) {
+ boolean updateOrientationFromAppTokensLocked(boolean inTransaction, int displayId) {
long ident = Binder.clearCallingIdentity();
try {
- int req = getOrientationLocked();
+ final int req = mRoot.getDisplayContent(displayId).getOrientation();
if (req != mLastOrientation) {
mLastOrientation = req;
//send a message to Policy indicating orientation change to take
//action like disabling/enabling sensors etc.,
mPolicy.setCurrentOrientationLw(req);
- if (updateRotationUncheckedLocked(inTransaction)) {
+ if (updateRotationUncheckedLocked(inTransaction, displayId)) {
// changed
return true;
}
@@ -3052,8 +2643,8 @@
}
@Override
- public int[] setNewConfiguration(Configuration config) {
- if (!checkCallingPermission(MANAGE_APP_TOKENS, "setNewConfiguration()")) {
+ public int[] setNewDisplayOverrideConfiguration(Configuration overrideConfig, int displayId) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "setNewDisplayOverrideConfiguration()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
@@ -3062,7 +2653,7 @@
mWaitingForConfig = false;
mLastFinishedFreezeSource = "new-config";
}
- return mRoot.setGlobalConfigurationIfNeeded(config);
+ return mRoot.setDisplayOverrideConfigurationIfNeeded(overrideConfig, displayId);
}
}
@@ -3368,7 +2959,8 @@
return false;
}
if (windowShowWallpaper) {
- if (mWallpaperControllerLocked.getWallpaperTarget() == null) {
+ if (wtoken.getDisplayContent().mWallpaperController.getWallpaperTarget()
+ == null) {
// If this theme is requesting a wallpaper, and the wallpaper
// is not currently visible, then this effectively serves as
// an opaque window and our starting window transition animation
@@ -3719,7 +3311,7 @@
if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
false /*updateInputWindows*/)) {
- mLayersController.assignLayersLocked(displayContent.getWindowList());
+ displayContent.assignWindowLayers(false /* setLayoutNeeded */);
}
mInputMonitor.setUpdateInputWindowsNeededLw();
@@ -4550,64 +4142,7 @@
performEnableScreen();
}
- private boolean checkWaitingForWindowsLocked() {
-
- boolean haveBootMsg = false;
- boolean haveApp = false;
- // if the wallpaper service is disabled on the device, we're never going to have
- // wallpaper, don't bother waiting for it
- boolean haveWallpaper = false;
- boolean wallpaperEnabled = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_enableWallpaperService)
- && !mOnlyCore;
- boolean haveKeyguard = true;
- // TODO(multidisplay): Expand to all displays?
- final WindowList windows = getDefaultWindowListLocked();
- final int N = windows.size();
- for (int i=0; i<N; i++) {
- WindowState w = windows.get(i);
- if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
- return true;
- }
- if (w.isDrawnLw()) {
- if (w.mAttrs.type == TYPE_BOOT_PROGRESS) {
- haveBootMsg = true;
- } else if (w.mAttrs.type == TYPE_APPLICATION
- || w.mAttrs.type == TYPE_DRAWN_APPLICATION) {
- haveApp = true;
- } else if (w.mAttrs.type == TYPE_WALLPAPER) {
- haveWallpaper = true;
- } else if (w.mAttrs.type == TYPE_STATUS_BAR) {
- haveKeyguard = mPolicy.isKeyguardDrawnLw();
- }
- }
- }
-
- if (DEBUG_SCREEN_ON || DEBUG_BOOT) {
- Slog.i(TAG_WM, "******** booted=" + mSystemBooted + " msg=" + mShowingBootMessages
- + " haveBoot=" + haveBootMsg + " haveApp=" + haveApp
- + " haveWall=" + haveWallpaper + " wallEnabled=" + wallpaperEnabled
- + " haveKeyguard=" + haveKeyguard);
- }
-
- // If we are turning on the screen to show the boot message,
- // don't do it until the boot message is actually displayed.
- if (!mSystemBooted && !haveBootMsg) {
- return true;
- }
-
- // If we are turning on the screen after the boot is completed
- // normally, don't do so until we have the application and
- // wallpaper.
- if (mSystemBooted && ((!haveApp && !haveKeyguard) ||
- (wallpaperEnabled && !haveWallpaper))) {
- return true;
- }
-
- return false;
- }
-
- public void performEnableScreen() {
+ private void performEnableScreen() {
synchronized(mWindowMap) {
if (DEBUG_BOOT) Slog.i(TAG_WM, "performEnableScreen: mDisplayEnabled=" + mDisplayEnabled
+ " mForceDisplayEnabled=" + mForceDisplayEnabled
@@ -4623,7 +4158,9 @@
}
// Don't enable the screen until all existing windows have been drawn.
- if (!mForceDisplayEnabled && checkWaitingForWindowsLocked()) {
+ if (!mForceDisplayEnabled
+ // TODO(multidisplay): Expand to all displays?
+ && getDefaultDisplayContentLocked().checkWaitingForWindows()) {
return;
}
@@ -4899,8 +4436,9 @@
}
try {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper");
- return screenshotApplicationsInner(null, Display.DEFAULT_DISPLAY, -1, -1, true, 1f,
- Bitmap.Config.ARGB_8888, true);
+ return screenshotApplicationsInner(null /* appToken */, DEFAULT_DISPLAY, -1 /* width */,
+ -1 /* height */, true /* includeFullDisplay */, 1f /* frameScale */,
+ Bitmap.Config.ARGB_8888, true /* wallpaperOnly */);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
@@ -4918,15 +4456,13 @@
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
- FgThread.getHandler().post(new Runnable() {
- @Override
- public void run() {
- Bitmap bm = screenshotApplicationsInner(null, Display.DEFAULT_DISPLAY, -1, -1,
- true, 1f, Bitmap.Config.ARGB_8888, false);
- try {
- receiver.send(bm);
- } catch (RemoteException e) {
- }
+ FgThread.getHandler().post(() -> {
+ Bitmap bm = screenshotApplicationsInner(null /* appToken */, DEFAULT_DISPLAY,
+ -1 /* width */, -1 /* height */, true /* includeFullDisplay */,
+ 1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */);
+ try {
+ receiver.send(bm);
+ } catch (RemoteException e) {
}
});
@@ -5193,7 +4729,7 @@
}
ScreenRotationAnimation screenRotationAnimation =
- mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
+ mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY);
final boolean inRotation = screenRotationAnimation != null &&
screenRotationAnimation.isAnimating();
if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM,
@@ -5327,16 +4863,17 @@
if (mDeferredRotationPauseCount > 0) {
mDeferredRotationPauseCount -= 1;
if (mDeferredRotationPauseCount == 0) {
- boolean changed = updateRotationUncheckedLocked(false);
+ // TODO(multi-display): Update rotation for different displays separately.
+ final int displayId = DEFAULT_DISPLAY;
+ final boolean changed = updateRotationUncheckedLocked(false, displayId);
if (changed) {
- mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+ mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
}
}
}
}
- private void updateRotationUnchecked(boolean alwaysSendConfiguration,
- boolean forceRelayout) {
+ private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
if(DEBUG_ORIENTATION) Slog.v(TAG_WM, "updateRotationUnchecked:"
+ " alwaysSendConfiguration=" + alwaysSendConfiguration
+ " forceRelayout=" + forceRelayout);
@@ -5345,8 +4882,10 @@
try {
final boolean rotationChanged;
+ // TODO(multi-display): Update rotation for different displays separately.
+ int displayId = DEFAULT_DISPLAY;
synchronized (mWindowMap) {
- rotationChanged = updateRotationUncheckedLocked(false);
+ rotationChanged = updateRotationUncheckedLocked(false, displayId);
if (!rotationChanged || forceRelayout) {
getDefaultDisplayContentLocked().setLayoutNeeded();
mWindowPlacerLocked.performSurfacePlacement();
@@ -5354,22 +4893,20 @@
}
if (rotationChanged || alwaysSendConfiguration) {
- sendNewConfiguration();
+ sendNewConfiguration(displayId);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
-
- // TODO(multidisplay): Rotate any display?
/**
- * Updates the current rotation.
+ * Updates the current rotation of the specified display.
*
- * Returns true if the rotation has been changed. In this case YOU
- * MUST CALL sendNewConfiguration() TO UNFREEZE THE SCREEN.
+ * Returns true if the rotation has been changed. In this case YOU MUST CALL
+ * {@link #sendNewConfiguration(int)} TO UNFREEZE THE SCREEN.
*/
- boolean updateRotationUncheckedLocked(boolean inTransaction) {
+ boolean updateRotationUncheckedLocked(boolean inTransaction, int displayId) {
if (mDeferredRotationPauseCount > 0) {
// Rotation updates have been paused temporarily. Defer the update until
// updates have been resumed.
@@ -5378,7 +4915,7 @@
}
ScreenRotationAnimation screenRotationAnimation =
- mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
+ mAnimator.getScreenRotationAnimationLocked(displayId);
if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
// Rotation updates cannot be performed while the previous rotation change
// animation is still in progress. Skip this update. We will try updating
@@ -5400,7 +4937,7 @@
return false;
}
- final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
final WindowList windows = displayContent.getWindowList();
final int oldRotation = mRotation;
@@ -5480,7 +5017,7 @@
startFreezingDisplayLocked(inTransaction, anim[0], anim[1]);
// startFreezingDisplayLocked can reset the ScreenRotationAnimation.
screenRotationAnimation =
- mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
+ mAnimator.getScreenRotationAnimationLocked(displayId);
} else {
// The screen rotation animation uses a screenshot to freeze the screen
// while windows resize underneath.
@@ -5498,7 +5035,7 @@
// the top of the method, the caller is obligated to call computeNewConfigurationLocked().
// By updating the Display info here it will be available to
// computeScreenConfigurationLocked later.
- updateDisplayAndOrientationLocked(mRoot.getConfiguration().uiMode);
+ updateDisplayAndOrientationLocked(displayContent.getConfiguration().uiMode, displayId);
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
if (!inTransaction) {
@@ -5566,7 +5103,7 @@
// Announce rotation only if we will not animate as we already have the
// windows in final state. Otherwise, we make this call at the rotation end.
if (screenRotationAnimation == null && mAccessibilityController != null
- && displayContent.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ && displayContent.getDisplayId() == DEFAULT_DISPLAY) {
mAccessibilityController.onRotationChangedLocked(getDefaultDisplayContentLocked(),
rotation);
}
@@ -6031,13 +5568,14 @@
}
/**
- * Instruct the Activity Manager to fetch new configurations, update global configuration
- * and broadcast changes to config-changed listeners if appropriate.
+ * Instruct the Activity Manager to fetch and update the current display's configuration and
+ * broadcast them to config-changed listeners if appropriate.
* NOTE: Can't be called with the window manager lock held since it call into activity manager.
*/
- void sendNewConfiguration() {
+ void sendNewConfiguration(int displayId) {
try {
- final boolean configUpdated = mActivityManager.updateConfiguration(null);
+ final boolean configUpdated = mActivityManager.updateDisplayOverrideConfiguration(
+ null /* values */, displayId);
if (!configUpdated) {
// Something changed (E.g. device rotation), but no configuration update is needed.
// E.g. changing device rotation by 180 degrees. Go ahead and perform surface
@@ -6047,7 +5585,7 @@
if (mWaitingForConfig) {
mWaitingForConfig = false;
mLastFinishedFreezeSource = "config-unchanged";
- getDefaultDisplayContentLocked().setLayoutNeeded();
+ mRoot.getDisplayContent(displayId).setLayoutNeeded();
mWindowPlacerLocked.performSurfacePlacement();
}
}
@@ -6056,18 +5594,18 @@
}
}
- public Configuration computeNewConfiguration() {
+ public Configuration computeNewConfiguration(int displayId) {
synchronized (mWindowMap) {
- return computeNewConfigurationLocked();
+ return computeNewConfigurationLocked(displayId);
}
}
- private Configuration computeNewConfigurationLocked() {
+ private Configuration computeNewConfigurationLocked(int displayId) {
if (!mDisplayReady) {
return null;
}
- Configuration config = new Configuration();
- computeScreenConfigurationLocked(config);
+ final Configuration config = new Configuration();
+ computeScreenConfigurationLocked(config, displayId);
return config;
}
@@ -6176,9 +5714,8 @@
}
/** Do not call if mDisplayReady == false */
- DisplayInfo updateDisplayAndOrientationLocked(int uiMode) {
- // TODO(multidisplay): For now, apply Configuration to main screen only.
- final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ private DisplayInfo updateDisplayAndOrientationLocked(int uiMode, int displayId) {
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
// Use the effective "visual" dimensions based on current rotation
final boolean rotated = (mRotation == Surface.ROTATION_90
@@ -6239,9 +5776,8 @@
}
/** Do not call if mDisplayReady == false */
- void computeScreenConfigurationLocked(Configuration config) {
- final DisplayInfo displayInfo = updateDisplayAndOrientationLocked(
- config.uiMode);
+ private void computeScreenConfigurationLocked(Configuration config, int displayId) {
+ final DisplayInfo displayInfo = updateDisplayAndOrientationLocked(config.uiMode, displayId);
final int dw = displayInfo.logicalWidth;
final int dh = displayInfo.logicalHeight;
@@ -6803,7 +6339,7 @@
synchronized(mWindowMap) {
// TODO(multidisplay): Accessibility supported only of default desiplay.
if (mAccessibilityController != null && getDefaultDisplayContentLocked()
- .getDisplayId() == Display.DEFAULT_DISPLAY) {
+ .getDisplayId() == DEFAULT_DISPLAY) {
accessibilityController = mAccessibilityController;
}
@@ -6880,11 +6416,9 @@
View view = null;
try {
- final Configuration overrideConfig =
- wtoken != null ? wtoken.getMergedOverrideConfiguration() : null;
view = mPolicy.addStartingWindow(wtoken.token, sd.pkg, sd.theme,
sd.compatInfo, sd.nonLocalizedLabel, sd.labelRes, sd.icon, sd.logo,
- sd.windowFlags, overrideConfig);
+ sd.windowFlags, wtoken.getMergedOverrideConfiguration());
} catch (Exception e) {
Slog.w(TAG_WM, "Exception when adding starting window", e);
}
@@ -7022,21 +6556,7 @@
case WINDOW_FREEZE_TIMEOUT: {
// TODO(multidisplay): Can non-default displays rotate?
synchronized (mWindowMap) {
- Slog.w(TAG_WM, "Window freeze timeout expired.");
- mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
- final WindowList windows = getDefaultWindowListLocked();
- int i = windows.size();
- while (i > 0) {
- i--;
- WindowState w = windows.get(i);
- if (w.mOrientationChanging) {
- w.mOrientationChanging = false;
- w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- - mDisplayFreezeTime);
- Slog.w(TAG_WM, "Force clearing orientation change: " + w);
- }
- }
- mWindowPlacerLocked.performSurfacePlacement();
+ getDefaultDisplayContentLocked().onWindowFreezeTimeout();
}
break;
}
@@ -7145,8 +6665,9 @@
}
case SEND_NEW_CONFIGURATION: {
- removeMessages(SEND_NEW_CONFIGURATION);
- sendNewConfiguration();
+ removeMessages(SEND_NEW_CONFIGURATION, msg.obj);
+ final int displayId = (Integer) msg.obj;
+ sendNewConfiguration(displayId);
break;
}
@@ -7337,7 +6858,7 @@
break;
case WALLPAPER_DRAW_PENDING_TIMEOUT: {
synchronized (mWindowMap) {
- if (mWallpaperControllerLocked.processWallpaperDrawPendingTimeout()) {
+ if (mRoot.mWallpaperController.processWallpaperDrawPendingTimeout()) {
mWindowPlacerLocked.performSurfacePlacement();
}
}
@@ -7483,44 +7004,9 @@
@Override
public boolean inputMethodClientHasFocus(IInputMethodClient client) {
synchronized (mWindowMap) {
- // The focus for the client is the window immediately below
- // where we would place the input method window.
- int idx = findDesiredInputMethodWindowIndexLocked(false);
- if (idx > 0) {
- // TODO(multidisplay): IMEs are only supported on the default display.
- WindowState imFocus = getDefaultWindowListLocked().get(idx-1);
- if (DEBUG_INPUT_METHOD) {
- Slog.i(TAG_WM, "Desired input method target: " + imFocus);
- Slog.i(TAG_WM, "Current focus: " + mCurrentFocus);
- Slog.i(TAG_WM, "Last focus: " + mLastFocus);
- }
- if (imFocus != null) {
- // This may be a starting window, in which case we still want
- // to count it as okay.
- if (imFocus.mAttrs.type == TYPE_APPLICATION_STARTING
- && imFocus.mAppToken != null) {
- // The client has definitely started, so it really should
- // have a window in this app token. Let's look for it.
- final WindowState w = imFocus.mAppToken.getFirstNonStartingWindow();
- if (w != null) {
- if (DEBUG_INPUT_METHOD) Slog.i(TAG_WM,
- "Switching to real app window: " + w);
- imFocus = w;
- }
- }
- if (DEBUG_INPUT_METHOD) {
- Slog.i(TAG_WM, "IM target client: " + imFocus.mSession.mClient);
- if (imFocus.mSession.mClient != null) {
- Slog.i(TAG_WM, "IM target client binder: "
- + imFocus.mSession.mClient.asBinder());
- Slog.i(TAG_WM, "Requesting client binder: " + client.asBinder());
- }
- }
- if (imFocus.mSession.mClient != null &&
- imFocus.mSession.mClient.asBinder() == client.asBinder()) {
- return true;
- }
- }
+ // TODO: multi-display
+ if (getDefaultDisplayContentLocked().inputMethodClientHasFocus(client)) {
+ return true;
}
// Okay, how about this... what is the current focus?
@@ -7569,7 +7055,7 @@
throw new SecurityException("Must hold permission " +
android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
- if (displayId != Display.DEFAULT_DISPLAY) {
+ if (displayId != DEFAULT_DISPLAY) {
throw new IllegalArgumentException("Can only set the default display");
}
final long ident = Binder.clearCallingIdentity();
@@ -7604,7 +7090,7 @@
throw new SecurityException("Must hold permission " +
android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
- if (displayId != Display.DEFAULT_DISPLAY) {
+ if (displayId != DEFAULT_DISPLAY) {
throw new IllegalArgumentException("Can only set the default display");
}
final long ident = Binder.clearCallingIdentity();
@@ -7687,7 +7173,7 @@
throw new SecurityException("Must hold permission " +
android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
- if (displayId != Display.DEFAULT_DISPLAY) {
+ if (displayId != DEFAULT_DISPLAY) {
throw new IllegalArgumentException("Can only set the default display");
}
final long ident = Binder.clearCallingIdentity();
@@ -7736,7 +7222,7 @@
throw new SecurityException("Must hold permission " +
android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
- if (displayId != Display.DEFAULT_DISPLAY) {
+ if (displayId != DEFAULT_DISPLAY) {
throw new IllegalArgumentException("Can only set the default display");
}
@@ -7767,7 +7253,7 @@
throw new SecurityException("Must hold permission " +
android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
- if (displayId != Display.DEFAULT_DISPLAY) {
+ if (displayId != DEFAULT_DISPLAY) {
throw new IllegalArgumentException("Can only set the default display");
}
@@ -7829,16 +7315,19 @@
configureDisplayPolicyLocked(displayContent);
displayContent.setLayoutNeeded();
- boolean configChanged = updateOrientationFromAppTokensLocked(false);
- final Configuration globalConfig = mRoot.getConfiguration();
- mTempConfiguration.setTo(globalConfig);
- computeScreenConfigurationLocked(mTempConfiguration);
- configChanged |= globalConfig.diff(mTempConfiguration) != 0;
+ final int displayId = displayContent.getDisplayId();
+ boolean configChanged = updateOrientationFromAppTokensLocked(false /* inTransaction */,
+ displayId);
+ final Configuration currentDisplayConfig = displayContent.getConfiguration();
+ mTempConfiguration.setTo(currentDisplayConfig);
+ computeScreenConfigurationLocked(mTempConfiguration, displayId);
+ configChanged |= currentDisplayConfig.diff(mTempConfiguration) != 0;
if (configChanged) {
mWaitingForConfig = true;
- startFreezingDisplayLocked(false, 0, 0);
- mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+ startFreezingDisplayLocked(false /* inTransaction */, 0 /* exitAnim */,
+ 0 /* enterAnim */);
+ mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
}
mWindowPlacerLocked.performSurfacePlacement();
@@ -7960,14 +7449,17 @@
}
mNoAnimationNotifyOnTransitionFinished.clear();
- mWallpaperControllerLocked.hideDeferredWallpapersIfNeeded();
+ // TODO: multi-display.
+ final DisplayContent dc = getDefaultDisplayContentLocked();
- getDefaultDisplayContentLocked().onAppTransitionDone();
+ dc.mWallpaperController.hideDeferredWallpapersIfNeeded();
+
+ dc.onAppTransitionDone();
changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT;
if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG_WM,
"Wallpaper layer changed: assigning layers + relayout");
- moveInputMethodWindowsIfNeededLocked(true);
+ dc.moveInputMethodWindowsIfNeeded(true);
mRoot.mWallpaperMayChange = true;
// Since the window list has been rebuilt, focus might have to be recomputed since the
// actual order of windows might have changed again.
@@ -8049,20 +7541,6 @@
}
}
- /** If a window that has an animation specifying a colored background and the current wallpaper
- * is visible, then the color goes *below* the wallpaper so we don't cause the wallpaper to
- * suddenly disappear. */
- int adjustAnimationBackground(WindowStateAnimator winAnimator) {
- WindowList windows = winAnimator.mWin.getWindowList();
- for (int i = windows.size() - 1; i >= 0; --i) {
- WindowState testWin = windows.get(i);
- if (testWin.mIsWallpaper && testWin.isVisibleNow()) {
- return testWin.mWinAnimator.mAnimLayer;
- }
- }
- return winAnimator.mAnimLayer;
- }
-
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
WindowState newFocus = mRoot.computeFocusedWindow();
if (mCurrentFocus != newFocus) {
@@ -8073,7 +7551,7 @@
mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
// TODO(multidisplay): Focused windows on default display only.
final DisplayContent displayContent = getDefaultDisplayContentLocked();
- final boolean imWindowChanged = moveInputMethodWindowsIfNeededLocked(
+ final boolean imWindowChanged = displayContent.moveInputMethodWindowsIfNeeded(
mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
&& mode != UPDATE_FOCUS_WILL_PLACE_SURFACES);
if (imWindowChanged) {
@@ -8098,7 +7576,7 @@
} else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {
// Client will do the layout, but we need to assign layers
// for handleNewWindowLocked() below.
- mLayersController.assignLayersLocked(displayContent.getWindowList());
+ displayContent.assignWindowLayers(false /* setLayoutNeeded */);
}
}
@@ -8180,16 +7658,7 @@
}
// Check whether the current screen contains any secure content.
- boolean isSecure = false;
- final WindowList windows = getDefaultWindowListLocked();
- final int N = windows.size();
- for (int i = 0; i < N; i++) {
- WindowState ws = windows.get(i);
- if (ws.isOnScreen() && (ws.mAttrs.flags & FLAG_SECURE) != 0) {
- isSecure = true;
- break;
- }
- }
+ boolean isSecure = displayContent.hasSecureWindowOnScreen();
// TODO(multidisplay): rotation on main screen only.
displayContent.updateDisplayInfo();
@@ -8276,7 +7745,7 @@
// to avoid inconsistent states. However, something interesting
// could have actually changed during that time so re-evaluate it
// now to catch that.
- configChanged = updateOrientationFromAppTokensLocked(false);
+ configChanged = updateOrientationFromAppTokensLocked(false, displayId);
// A little kludge: a lot could have happened while the
// display was frozen, so now that we are coming back we
@@ -8290,11 +7759,11 @@
if (updateRotation) {
if (DEBUG_ORIENTATION) Slog.d(TAG_WM, "Performing post-rotate rotation");
- configChanged |= updateRotationUncheckedLocked(false);
+ configChanged |= updateRotationUncheckedLocked(false, displayId);
}
if (configChanged) {
- mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+ mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
}
}
@@ -8395,8 +7864,8 @@
}
}
- // TOOD(multidisplay): StatusBar on multiple screens?
- boolean updateStatusBarVisibilityLocked(int visibility) {
+ // TODO(multidisplay): StatusBar on multiple screens?
+ private boolean updateStatusBarVisibilityLocked(int visibility) {
if (mLastDispatchedSystemUiVisibility == visibility) {
return false;
}
@@ -8409,26 +7878,7 @@
mLastDispatchedSystemUiVisibility = visibility;
mInputManager.setSystemUiVisibility(visibility);
- final WindowList windows = getDefaultWindowListLocked();
- final int N = windows.size();
- for (int i = 0; i < N; i++) {
- WindowState ws = windows.get(i);
- try {
- int curValue = ws.mSystemUiVisibility;
- int diff = (curValue ^ visibility) & globalDiff;
- int newValue = (curValue&~diff) | (visibility&diff);
- if (newValue != curValue) {
- ws.mSeq++;
- ws.mSystemUiVisibility = newValue;
- }
- if (newValue != curValue || ws.mAttrs.hasSystemUiListeners) {
- ws.mClient.dispatchSystemUiVisibilityChanged(ws.mSeq,
- visibility, newValue, diff);
- }
- } catch (RemoteException e) {
- // so sorry
- }
- }
+ getDefaultDisplayContentLocked().updateSystemUiVisibility(visibility, globalDiff);
return true;
}
@@ -8573,7 +8023,7 @@
private void dumpTokensLocked(PrintWriter pw, boolean dumpAll) {
pw.println("WINDOW MANAGER TOKENS (dumpsys window tokens)");
mRoot.dumpTokens(pw, dumpAll);
- mWallpaperControllerLocked.dumpTokens(pw, " ", dumpAll);
+ mRoot.mWallpaperController.dumpTokens(pw, " ", dumpAll);
if (!mFinishedStarting.isEmpty()) {
pw.println();
pw.println(" Finishing start of application tokens:");
@@ -8757,8 +8207,7 @@
pw.print(" mInputMethodWindow="); pw.println(mInputMethodWindow);
}
mWindowPlacerLocked.dump(pw, " ");
- mWallpaperControllerLocked.dump(pw, " ");
- mLayersController.dump(pw, " ");
+ mRoot.mWallpaperController.dump(pw, " ");
pw.print(" mSystemBooted="); pw.print(mSystemBooted);
pw.print(" mDisplayEnabled="); pw.println(mDisplayEnabled);
@@ -9022,7 +8471,7 @@
}
// TODO: All the display method below should probably be moved into the RootWindowContainer...
- public void createDisplayContentLocked(final Display display) {
+ private void createDisplayContentLocked(final Display display) {
if (display == null) {
throw new IllegalArgumentException("getDisplayContent: display must not be null");
}
@@ -9030,28 +8479,14 @@
}
// There is an inherent assumption that this will never return null.
- public DisplayContent getDefaultDisplayContentLocked() {
- return mRoot.getDisplayContentOrCreate(Display.DEFAULT_DISPLAY);
+ DisplayContent getDefaultDisplayContentLocked() {
+ return mRoot.getDisplayContentOrCreate(DEFAULT_DISPLAY);
}
- public WindowList getDefaultWindowListLocked() {
- return getDefaultDisplayContentLocked().getWindowList();
- }
-
- public DisplayInfo getDefaultDisplayInfoLocked() {
+ private DisplayInfo getDefaultDisplayInfoLocked() {
return getDefaultDisplayContentLocked().getDisplayInfo();
}
- /**
- * Return the list of WindowStates associated on the passed display.
- * @param displayId The screen to return windows from.
- * @return The list of WindowStates on the screen, or null if the there is no screen.
- */
- WindowList getWindowListLocked(final int displayId) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
- return displayContent != null ? displayContent.getWindowList() : null;
- }
-
public void onDisplayAdded(int displayId) {
mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_ADDED, displayId, 0));
}
@@ -9453,8 +8888,9 @@
if (DEBUG_ORIENTATION) {
Slog.i(TAG, "Performing post-rotate rotation after seamless rotation");
}
- if (updateRotationUncheckedLocked(false)) {
- mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+ final int displayId = w.getDisplayId();
+ if (updateRotationUncheckedLocked(false, displayId)) {
+ mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
}
}
}
@@ -9581,24 +9017,7 @@
boolean allWindowsDrawn = false;
synchronized (mWindowMap) {
mWaitingForDrawnCallback = callback;
- final WindowList windows = getDefaultWindowListLocked();
- for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
- final WindowState win = windows.get(winNdx);
- final boolean isForceHiding = mPolicy.isForceHiding(win.mAttrs);
- final boolean keyguard = mPolicy.isKeyguardHostWindow(win.mAttrs);
- if (win.isVisibleLw()
- && (win.mAppToken != null || isForceHiding || keyguard)) {
- win.mWinAnimator.mDrawState = DRAW_PENDING;
- // Force add to mResizingWindows.
- win.mLastContentInsets.set(-1, -1, -1, -1);
- mWaitingForDrawn.add(win);
-
- // No need to wait for the windows below Keyguard.
- if (isForceHiding) {
- break;
- }
- }
- }
+ getDefaultDisplayContentLocked().waitForAllWindowsDrawn();
mWindowPlacerLocked.requestTraversal();
mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT);
if (mWaitingForDrawn.isEmpty()) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index fa4dbc4..a7b46111 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -38,7 +38,6 @@
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;
@@ -86,7 +85,6 @@
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
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.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
@@ -890,8 +888,10 @@
getDisplayContent().getLogicalDisplayRect(mTmpRect);
// Override right and/or bottom insets in case if the frame doesn't fit the screen in
// non-fullscreen mode.
- boolean overrideRightInset = !fullscreenTask && mFrame.right > mTmpRect.right;
- boolean overrideBottomInset = !fullscreenTask && mFrame.bottom > mTmpRect.bottom;
+ boolean overrideRightInset = !windowsAreFloating && !fullscreenTask &&
+ mFrame.right > mTmpRect.right;
+ boolean overrideBottomInset = !windowsAreFloating && !fullscreenTask &&
+ mFrame.bottom > mTmpRect.bottom;
mContentInsets.set(mContentFrame.left - mFrame.left,
mContentFrame.top - mFrame.top,
overrideRightInset ? mTmpRect.right - mContentFrame.right
@@ -941,7 +941,7 @@
final DisplayContent displayContent = getDisplayContent();
if (displayContent != null) {
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- mService.mWallpaperControllerLocked.updateWallpaperOffset(
+ getDisplayContent().mWallpaperController.updateWallpaperOffset(
this, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
}
}
@@ -1009,30 +1009,7 @@
@Override
public boolean getNeedsMenuLw(WindowManagerPolicy.WindowState bottom) {
- int index = -1;
- WindowState ws = this;
- WindowList windows = getWindowList();
- while (true) {
- if (ws.mAttrs.needsMenuKey != WindowManager.LayoutParams.NEEDS_MENU_UNSET) {
- return ws.mAttrs.needsMenuKey == WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE;
- }
- // If we reached the bottom of the range of windows we are considering,
- // assume no menu is needed.
- if (ws == bottom) {
- return false;
- }
- // The current window hasn't specified whether menu key is needed;
- // look behind it.
- // First, we may need to determine the starting position.
- if (index < 0) {
- index = windows.indexOf(ws);
- }
- index--;
- if (index < 0) {
- return false;
- }
- ws = windows.get(index);
- }
+ return getDisplayContent().getNeedsMenu(this, bottom);
}
@Override
@@ -1692,7 +1669,7 @@
//TODO (multidisplay): Accessibility supported only for the default display.
if (mService.mAccessibilityController != null
- && getDisplayContent().getDisplayId() == Display.DEFAULT_DISPLAY) {
+ && getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) {
mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
}
@@ -1784,14 +1761,14 @@
mReplacementWindow.mSkipEnterAnimationForSeamlessReplacement = false;
}
+ final DisplayContent dc = getDisplayContent();
if (mService.mInputMethodTarget == this) {
- mService.moveInputMethodWindowsIfNeededLocked(false);
+ dc.moveInputMethodWindowsIfNeeded(false);
}
final int type = mAttrs.type;
if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) {
- final DisplayContent displaycontent = getDisplayContent();
- displaycontent.mTapExcludedWindows.remove(this);
+ dc.mTapExcludedWindows.remove(this);
}
mPolicy.removeWindowLw(this);
@@ -1853,6 +1830,8 @@
// Visibility of the removed window. Will be used later to update orientation later on.
boolean wasVisible = false;
+ final int displayId = getDisplayId();
+
// First, see if we need to run an animation. If we do, we have to hold off on removing the
// window until the animation is done. If the display is frozen, just remove immediately,
// since the animation wouldn't be seen.
@@ -1913,8 +1892,7 @@
mAnimatingExit = true;
}
//TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null
- && getDisplayId() == Display.DEFAULT_DISPLAY) {
+ if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
}
}
@@ -1944,8 +1922,8 @@
removeImmediately();
// Removing a visible window will effect the computed orientation
// So just update orientation if needed.
- if (wasVisible && mService.updateOrientationFromAppTokensLocked(false)) {
- mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);
+ if (wasVisible && mService.updateOrientationFromAppTokensLocked(false, displayId)) {
+ mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
}
mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
Binder.restoreCallingIdentity(origId);
@@ -1986,6 +1964,17 @@
return 0;
}
+ int getSpecialWindowAnimLayerAdjustment() {
+ int specialAdjustment = 0;
+ if (mIsImWindow) {
+ specialAdjustment = getDisplayContent().mInputMethodAnimLayerAdjustment;
+ } else if (mIsWallpaper) {
+ specialAdjustment = getDisplayContent().mWallpaperController.getAnimLayerAdjustment();
+ }
+
+ return mLayer + specialAdjustment;
+ }
+
void scheduleAnimationIfDimming() {
final DisplayContent dc = getDisplayContent();
if (dc == null) {
@@ -2562,7 +2551,7 @@
clearAnimatingWithSavedSurface();
mDestroying = true;
mWinAnimator.hide("stopUsingSavedSurface");
- mService.mWallpaperControllerLocked.hideWallpapers(this);
+ getDisplayContent().mWallpaperController.hideWallpapers(this);
}
void markSavedSurfaceExiting() {
@@ -2948,8 +2937,7 @@
applyInsets(outRegion, frame, mGivenVisibleInsets);
break;
case TOUCHABLE_INSETS_REGION: {
- final Region givenTouchableRegion = mGivenTouchableRegion;
- outRegion.set(givenTouchableRegion);
+ outRegion.set(mGivenTouchableRegion);
outRegion.translate(frame.left, frame.top);
break;
}
@@ -2957,7 +2945,7 @@
cropRegionToStackBoundsIfNeeded(outRegion);
}
- void cropRegionToStackBoundsIfNeeded(Region region) {
+ private void cropRegionToStackBoundsIfNeeded(Region region) {
final Task task = getTask();
if (task == null || !task.cropWindowsToStackBounds()) {
return;
@@ -2972,13 +2960,6 @@
region.op(mTmpRect, Region.Op.INTERSECT);
}
- // TODO: This is one reason why WindowList are bad...prime candidate for removal once we
- // figure-out a good way to replace WindowList with WindowContainer hierarchy.
- WindowList getWindowList() {
- final DisplayContent displayContent = getDisplayContent();
- return displayContent == null ? null : displayContent.getWindowList();
- }
-
/**
* Report a focus change. Must be called with no locks held, and consistently
* from the same serialized thread (such as dispatched from a handler).
@@ -3056,8 +3037,7 @@
}
//TODO (multidisplay): Accessibility supported only for the default display.
- if (mService.mAccessibilityController != null
- && getDisplayId() == Display.DEFAULT_DISPLAY) {
+ if (mService.mAccessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) {
mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
}
@@ -3867,7 +3847,7 @@
// Or, it is probably not going to matter anyways if we are successful in getting rid of
// the WindowList concept.
int reAddWindow(int index) {
- final WindowList windows = getWindowList();
+ final DisplayContent dc = getDisplayContent();
// Adding child windows relies on child windows being ordered by mSubLayer using
// {@link #sWindowSubLayerComparator}.
final int childCount = mChildren.size();
@@ -3878,51 +3858,25 @@
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM,
"Re-adding child window at " + index + ": " + child);
mRebuilding = false;
- windows.add(index, this);
+ dc.addToWindowList(this, index);
index++;
winAdded = true;
}
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at " + index + ": " + child);
child.mRebuilding = false;
- windows.add(index, child);
+ dc.addToWindowList(child, index);
index++;
}
if (!winAdded) {
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at " + index + ": " + this);
mRebuilding = false;
- windows.add(index, this);
+ dc.addToWindowList(this, index);
index++;
}
mService.mWindowsChanged = true;
return index;
}
- int removeFromWindowList(int interestingPos) {
- final WindowList windows = getWindowList();
- int wpos = windows.indexOf(this);
- if (wpos < 0) {
- return interestingPos;
- }
-
- if (wpos < interestingPos) interestingPos--;
- if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Temp removing at " + wpos + ": " + this);
- windows.remove(wpos);
- mService.mWindowsChanged = true;
- int childCount = mChildren.size();
- while (childCount > 0) {
- childCount--;
- final WindowState cw = mChildren.get(childCount);
- int cpos = windows.indexOf(cw);
- if (cpos >= 0) {
- if (cpos < interestingPos) interestingPos--;
- if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM,
- "Temp removing child at " + cpos + ": " + cw);
- windows.remove(cpos);
- }
- }
- return interestingPos;
- }
-
boolean isWindowAnimationSet() {
if (mWinAnimator.isWindowAnimationSet()) {
return true;
@@ -4004,7 +3958,7 @@
}
}
mAnimatingExit = false;
- mService.mWallpaperControllerLocked.hideWallpapers(this);
+ getDisplayContent().mWallpaperController.hideWallpapers(this);
}
boolean clearAnimatingFlags() {
@@ -4071,7 +4025,7 @@
*/
void dispatchWallpaperVisibility(final boolean visible) {
final boolean hideAllowed =
- mService.mWallpaperControllerLocked.mDeferredHideWallpaper == null;
+ getDisplayContent().mWallpaperController.mDeferredHideWallpaper == null;
// Only send notification if the visibility actually changed and we are not trying to hide
// the wallpaper when we are deferring hiding of the wallpaper.
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 53fb99b..2aeb50b 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -262,7 +262,7 @@
mSession = win.mSession;
mAttrType = win.mAttrs.type;
mIsWallpaper = win.mIsWallpaper;
- mWallpaperControllerLocked = mService.mWallpaperControllerLocked;
+ mWallpaperControllerLocked = mService.mRoot.mWallpaperController;
}
public void setAnimation(Animation anim, long startTime, int stackClip) {
@@ -461,8 +461,7 @@
if (mAnimator.mWindowDetachedWallpaper == mWin) {
mAnimator.mWindowDetachedWallpaper = null;
}
- mAnimLayer = mWin.mLayer
- + mService.mLayersController.getSpecialWindowAnimLayerAdjustment(mWin);
+ mAnimLayer = mWin.getSpecialWindowAnimLayerAdjustment();
if (DEBUG_LAYERS) Slog.v(TAG, "Stepping win " + this + " anim layer: " + mAnimLayer);
mHasTransformation = false;
mHasLocalTransformation = false;
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 368484a..c48a585 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -58,6 +58,12 @@
private float mSurfaceW = 0;
private float mSurfaceH = 0;
+ // Initialize to the identity matrix.
+ private float mLastDsdx = 1;
+ private float mLastDtdx = 0;
+ private float mLastDsdy = 0;
+ private float mLastDtdy = 1;
+
private float mSurfaceAlpha = 0;
private int mSurfaceLayer = 0;
@@ -285,6 +291,17 @@
void setMatrixInTransaction(float dsdx, float dtdx, float dsdy, float dtdy,
boolean recoveringMemory) {
+ final boolean matrixChanged = mLastDsdx != dsdx || mLastDtdx != dtdx ||
+ mLastDsdy != dsdy || mLastDtdy != dtdy;
+ if (!matrixChanged) {
+ return;
+ }
+
+ mLastDsdx = dsdx;
+ mLastDtdx = dtdx;
+ mLastDsdy = dsdy;
+ mLastDtdy = dtdy;
+
try {
if (SHOW_TRANSACTIONS) logSurface(
"MATRIX [" + dsdx + "," + dtdx + "," + dsdy + "," + dtdy + "]", null);
@@ -300,7 +317,6 @@
mAnimator.reclaimSomeSurfaceMemory("matrix", true);
}
}
- return;
}
boolean setSizeInTransaction(int width, int height, boolean recoveringMemory) {
@@ -337,6 +353,10 @@
mSurfaceControl.setAlpha(alpha);
mSurfaceLayer = layer;
mSurfaceControl.setLayer(layer);
+ mLastDsdx = dsdx;
+ mLastDtdx = dtdx;
+ mLastDsdy = dsdy;
+ mLastDtdy = dtdy;
mSurfaceControl.setMatrix(
dsdx, dtdx, dsdy, dtdy);
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 285a75c..04e00c4 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -96,7 +96,7 @@
public WindowSurfacePlacer(WindowManagerService service) {
mService = service;
- mWallpaperControllerLocked = mService.mWallpaperControllerLocked;
+ mWallpaperControllerLocked = mService.mRoot.mWallpaperController;
}
/**
@@ -383,10 +383,9 @@
}
/**
- * @param windows List of windows on default display.
* @return bitmap indicating if another pass through layout must be made.
*/
- int handleAppTransitionReadyLocked(WindowList windows) {
+ int handleAppTransitionReadyLocked() {
int appsCount = mService.mOpeningApps.size();
if (!transitionGoodToGo(appsCount)) {
return 0;
@@ -429,7 +428,7 @@
// (like the clearAnimatingFlags() above) might affect wallpaper target result.
// Or, the opening app window should be a wallpaper target.
mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(displayContent,
- mService.mOpeningApps, windows);
+ mService.mOpeningApps);
final WindowState lowerWallpaperTarget =
mWallpaperControllerLocked.getLowerWallpaperTarget();
@@ -530,9 +529,11 @@
displayContent.setLayoutNeeded();
// TODO(multidisplay): IMEs are only supported on the default display.
- if (windows == mService.getDefaultWindowListLocked()
- && !mService.moveInputMethodWindowsIfNeededLocked(true)) {
- mService.mLayersController.assignLayersLocked(windows);
+ // TODO: Probably not needed once the window list always has the right z-ordering
+ // when the window hierarchy is updated.
+ final DisplayContent dc = mService.getDefaultDisplayContentLocked();
+ if (!dc.moveInputMethodWindowsIfNeeded(true)) {
+ dc.assignWindowLayers(false /*setLayoutNeeded*/);
}
mService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
true /*updateInputWindows*/);
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 218972a..afcdc41 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -140,7 +140,7 @@
highestAnimLayer = winHighestAnimLayer;
}
if (w == mService.mInputMethodTarget && !mService.mInputMethodTargetWaitingAnim) {
- mService.mLayersController.setInputMethodAnimLayerAdjustment(adj);
+ mDisplayContent.setInputMethodAnimLayerAdjustment(adj);
}
}
return highestAnimLayer;
@@ -188,11 +188,11 @@
}
void addImeWindow(WindowState win) {
- int pos = mService.findDesiredInputMethodWindowIndexLocked(true);
+ int pos = mDisplayContent.findDesiredInputMethodWindowIndex(true);
if (pos < 0) {
addWindow(win);
- mService.moveInputMethodDialogsLocked(pos);
+ mDisplayContent.moveInputMethodDialogs(pos);
return;
}
@@ -203,7 +203,7 @@
addChild(win, null);
}
mService.mWindowsChanged = true;
- mService.moveInputMethodDialogsLocked(pos + 1);
+ mDisplayContent.moveInputMethodDialogs(pos + 1);
}
/** Return the first window in the token window list that isn't a starting window or null. */
@@ -269,7 +269,7 @@
}
void updateWallpaperOffset(int dw, int dh, boolean sync) {
- final WallpaperController wallpaperController = mService.mWallpaperControllerLocked;
+ final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
final WindowState wallpaper = mChildren.get(wallpaperNdx);
if (wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, sync)) {
@@ -294,7 +294,7 @@
mDisplayContent.setLayoutNeeded();
}
- final WallpaperController wallpaperController = mService.mWallpaperControllerLocked;
+ final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
final WindowState wallpaper = mChildren.get(wallpaperNdx);
if (visible) {
@@ -317,7 +317,7 @@
mDisplayContent.setLayoutNeeded();
}
- final WallpaperController wallpaperController = mService.mWallpaperControllerLocked;
+ final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
final WindowState wallpaper = mChildren.get(wallpaperNdx);
@@ -427,11 +427,13 @@
@Override
void removeImmediately() {
- super.removeImmediately();
if (mDisplayContent != null) {
mDisplayContent.removeWindowToken(token);
mService.mRoot.removeWindowTokenIfPossible(token);
}
+ // Needs to occur after the token is removed from the display above to avoid attempt at
+ // duplicate removal of this window container from it's parent.
+ super.removeImmediately();
}
void onDisplayChanged(DisplayContent dc) {
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index d328ade..121067a 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -70,3 +70,4 @@
android.hardware.power@1.0 \
android.hardware.vibrator@1.0 \
android.hardware.light@2.0 \
+ android.hardware.vr@1.0 \
diff --git a/services/core/jni/com_android_server_vr_VrManagerService.cpp b/services/core/jni/com_android_server_vr_VrManagerService.cpp
index 1aba43b2..e06e051 100644
--- a/services/core/jni/com_android_server_vr_VrManagerService.cpp
+++ b/services/core/jni/com_android_server_vr_VrManagerService.cpp
@@ -20,44 +20,41 @@
#include <jni.h>
#include <JNIHelp.h>
+#include <android/hardware/vr/1.0/IVr.h>
#include <utils/Errors.h>
#include <utils/Log.h>
-#include <hardware/hardware.h>
-#include <hardware/vr.h>
namespace android {
-static vr_module_t *gVrHardwareModule = NULL;
+using ::android::hardware::vr::V1_0::IVr;
+static sp<IVr> gVr;
static void init_native(JNIEnv* /* env */, jclass /* clazz */) {
- if (gVrHardwareModule != NULL) {
+ // TODO(b/31632518)
+ if (gVr != nullptr) {
// This call path should never be hit.
- ALOGE("%s: May not initialize VR hardware module more than once!", __FUNCTION__);
+ ALOGE("%s: May not initialize IVr interface module more than once!", __FUNCTION__);
return;
}
- int err = hw_get_module(VR_HARDWARE_MODULE_ID, (hw_module_t const**)&gVrHardwareModule);
- if (err) {
- ALOGW("%s: Could not open VR hardware module, error %s (%d).", __FUNCTION__,
- strerror(-err), err);
+ gVr = IVr::getService("vr");
+ if (gVr == nullptr) {
+ ALOGW("%s: Could not open IVr interface", __FUNCTION__);
return;
}
- // Call init method if implemented.
- if (gVrHardwareModule->init) {
- gVrHardwareModule->init(gVrHardwareModule);
- }
+ gVr->init();
}
static void setVrMode_native(JNIEnv* /* env */, jclass /* clazz */, jboolean enabled) {
- if (gVrHardwareModule == NULL) {
+ if (gVr == nullptr) {
// There is no VR hardware module implemented, do nothing.
return;
}
// Call set_vr_mode method, this must be implemented if the HAL exists.
- gVrHardwareModule->set_vr_mode(gVrHardwareModule, static_cast<bool>(enabled));
+ gVr->setVrMode(static_cast<bool>(enabled));
}
static const JNINativeMethod method_table[] = {
diff --git a/services/core/proto/ipconnectivity.proto b/services/core/proto/ipconnectivity.proto
index e0d7f09..29b318f 100644
--- a/services/core/proto/ipconnectivity.proto
+++ b/services/core/proto/ipconnectivity.proto
@@ -53,6 +53,7 @@
// The event type code of the probe, represented by constants defined in
// android.net.metrics.IpReachabilityEvent.
+ // NUD_FAILED_ORGANIC and PROVISIONING_LOST_ORGANIC recorded since version 1.
optional int32 event_type = 2;
};
@@ -126,11 +127,12 @@
// Lifetime duration in milliseconds of a DhcpClient state, or transition
// time in milliseconds between specific pairs of DhcpClient's states.
- // Only populated when state_transition is populated.
+ // Only populated since version 1, when state_transition is populated.
optional int32 duration_ms = 4;
}
// Represents the generation of an Android Packet Filter program.
+// Since version 1.
message ApfProgramEvent {
// Lifetime of the program in seconds.
optional int64 lifetime = 1;
@@ -154,6 +156,7 @@
// Represents Router Advertisement listening statistics for an interface with
// Android Packet Filter enabled.
+// Since version 1.
message ApfStatistics {
// The time interval in milliseconds these stastistics cover.
optional int64 duration_ms = 1;
@@ -183,6 +186,7 @@
// Represents the reception of a Router Advertisement packet for an interface
// with Android Packet Filter enabled.
+// Since version 1.
message RaEvent {
// All lifetime values are expressed in seconds. The default value for an
// option lifetime that was not present in the RA option list is -1.
@@ -271,4 +275,11 @@
// The number of events that had to be dropped due to a full buffer.
optional int32 dropped_events = 2;
+
+ // The version number of the metrics events being collected.
+ // nyc-dev: not populated, implicitly 0
+ // nyc-dr1: not populated, implicitly 1 (sailfish and marlin only)
+ // nyc-mr1: not populated, implicitly 1
+ // nyc-mr2: 2
+ optional int32 version = 3;
};
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 88d8dd0..eb85e89 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -22,6 +22,7 @@
import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
import static android.content.pm.PackageManager.GET_UNINSTALLED_PACKAGES;
+import static com.android.internal.logging.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
@@ -50,6 +51,7 @@
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.IDevicePolicyManager;
+import android.app.admin.PasswordMetrics;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
import android.app.admin.SystemUpdatePolicy;
@@ -130,6 +132,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
@@ -317,6 +320,12 @@
*/
private static final long MINIMUM_STRONG_AUTH_TIMEOUT_MS = 1 * 60 * 60 * 1000; // 1h
+ /**
+ * Strings logged with {@link #PROVISIONING_ENTRY_POINT_ADB}.
+ */
+ private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
+ private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
+
final Context mContext;
final Injector mInjector;
final IPackageManager mIPackageManager;
@@ -417,14 +426,7 @@
}
public static class DevicePolicyData {
- int mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
- int mActivePasswordLength = 0;
- int mActivePasswordUpperCase = 0;
- int mActivePasswordLowerCase = 0;
- int mActivePasswordLetters = 0;
- int mActivePasswordNumeric = 0;
- int mActivePasswordSymbols = 0;
- int mActivePasswordNonLetter = 0;
+ @NonNull PasswordMetrics mActivePasswordMetrics = new PasswordMetrics();
int mFailedPasswordAttempts = 0;
int mUserHandle;
@@ -590,31 +592,23 @@
final DeviceAdminInfo info;
- int passwordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
- static final int DEF_MINIMUM_PASSWORD_LENGTH = 0;
- int minimumPasswordLength = DEF_MINIMUM_PASSWORD_LENGTH;
static final int DEF_PASSWORD_HISTORY_LENGTH = 0;
int passwordHistoryLength = DEF_PASSWORD_HISTORY_LENGTH;
- static final int DEF_MINIMUM_PASSWORD_UPPER_CASE = 0;
- int minimumPasswordUpperCase = DEF_MINIMUM_PASSWORD_UPPER_CASE;
-
- static final int DEF_MINIMUM_PASSWORD_LOWER_CASE = 0;
- int minimumPasswordLowerCase = DEF_MINIMUM_PASSWORD_LOWER_CASE;
-
+ static final int DEF_MINIMUM_PASSWORD_LENGTH = 0;
static final int DEF_MINIMUM_PASSWORD_LETTERS = 1;
- int minimumPasswordLetters = DEF_MINIMUM_PASSWORD_LETTERS;
-
+ static final int DEF_MINIMUM_PASSWORD_UPPER_CASE = 0;
+ static final int DEF_MINIMUM_PASSWORD_LOWER_CASE = 0;
static final int DEF_MINIMUM_PASSWORD_NUMERIC = 1;
- int minimumPasswordNumeric = DEF_MINIMUM_PASSWORD_NUMERIC;
-
static final int DEF_MINIMUM_PASSWORD_SYMBOLS = 1;
- int minimumPasswordSymbols = DEF_MINIMUM_PASSWORD_SYMBOLS;
-
static final int DEF_MINIMUM_PASSWORD_NON_LETTER = 0;
- int minimumPasswordNonLetter = DEF_MINIMUM_PASSWORD_NON_LETTER;
+ @NonNull
+ PasswordMetrics minimumPasswordMetrics = new PasswordMetrics(
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, DEF_MINIMUM_PASSWORD_LENGTH,
+ DEF_MINIMUM_PASSWORD_LETTERS, DEF_MINIMUM_PASSWORD_UPPER_CASE,
+ DEF_MINIMUM_PASSWORD_LOWER_CASE, DEF_MINIMUM_PASSWORD_NUMERIC,
+ DEF_MINIMUM_PASSWORD_SYMBOLS, DEF_MINIMUM_PASSWORD_NON_LETTER);
static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0;
long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
@@ -720,13 +714,15 @@
out.startTag(null, TAG_POLICIES);
info.writePoliciesToXml(out);
out.endTag(null, TAG_POLICIES);
- if (passwordQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ if (minimumPasswordMetrics.quality
+ != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
out.startTag(null, TAG_PASSWORD_QUALITY);
- out.attribute(null, ATTR_VALUE, Integer.toString(passwordQuality));
+ out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.quality));
out.endTag(null, TAG_PASSWORD_QUALITY);
- if (minimumPasswordLength != DEF_MINIMUM_PASSWORD_LENGTH) {
+ if (minimumPasswordMetrics.length != DEF_MINIMUM_PASSWORD_LENGTH) {
out.startTag(null, TAG_MIN_PASSWORD_LENGTH);
- out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordLength));
+ out.attribute(
+ null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.length));
out.endTag(null, TAG_MIN_PASSWORD_LENGTH);
}
if(passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
@@ -734,34 +730,40 @@
out.attribute(null, ATTR_VALUE, Integer.toString(passwordHistoryLength));
out.endTag(null, TAG_PASSWORD_HISTORY_LENGTH);
}
- if (minimumPasswordUpperCase != DEF_MINIMUM_PASSWORD_UPPER_CASE) {
+ if (minimumPasswordMetrics.upperCase != DEF_MINIMUM_PASSWORD_UPPER_CASE) {
out.startTag(null, TAG_MIN_PASSWORD_UPPERCASE);
- out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordUpperCase));
+ out.attribute(
+ null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.upperCase));
out.endTag(null, TAG_MIN_PASSWORD_UPPERCASE);
}
- if (minimumPasswordLowerCase != DEF_MINIMUM_PASSWORD_LOWER_CASE) {
+ if (minimumPasswordMetrics.lowerCase != DEF_MINIMUM_PASSWORD_LOWER_CASE) {
out.startTag(null, TAG_MIN_PASSWORD_LOWERCASE);
- out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordLowerCase));
+ out.attribute(
+ null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.lowerCase));
out.endTag(null, TAG_MIN_PASSWORD_LOWERCASE);
}
- if (minimumPasswordLetters != DEF_MINIMUM_PASSWORD_LETTERS) {
+ if (minimumPasswordMetrics.letters != DEF_MINIMUM_PASSWORD_LETTERS) {
out.startTag(null, TAG_MIN_PASSWORD_LETTERS);
- out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordLetters));
+ out.attribute(
+ null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.letters));
out.endTag(null, TAG_MIN_PASSWORD_LETTERS);
}
- if (minimumPasswordNumeric != DEF_MINIMUM_PASSWORD_NUMERIC) {
+ if (minimumPasswordMetrics.numeric != DEF_MINIMUM_PASSWORD_NUMERIC) {
out.startTag(null, TAG_MIN_PASSWORD_NUMERIC);
- out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordNumeric));
+ out.attribute(
+ null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.numeric));
out.endTag(null, TAG_MIN_PASSWORD_NUMERIC);
}
- if (minimumPasswordSymbols != DEF_MINIMUM_PASSWORD_SYMBOLS) {
+ if (minimumPasswordMetrics.symbols != DEF_MINIMUM_PASSWORD_SYMBOLS) {
out.startTag(null, TAG_MIN_PASSWORD_SYMBOLS);
- out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordSymbols));
+ out.attribute(
+ null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.symbols));
out.endTag(null, TAG_MIN_PASSWORD_SYMBOLS);
}
- if (minimumPasswordNonLetter > DEF_MINIMUM_PASSWORD_NON_LETTER) {
+ if (minimumPasswordMetrics.nonLetter > DEF_MINIMUM_PASSWORD_NON_LETTER) {
out.startTag(null, TAG_MIN_PASSWORD_NONLETTER);
- out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordNonLetter));
+ out.attribute(
+ null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.nonLetter));
out.endTag(null, TAG_MIN_PASSWORD_NONLETTER);
}
}
@@ -960,31 +962,31 @@
if (TAG_POLICIES.equals(tag)) {
info.readPoliciesFromXml(parser);
} else if (TAG_PASSWORD_QUALITY.equals(tag)) {
- passwordQuality = Integer.parseInt(
+ minimumPasswordMetrics.quality = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_LENGTH.equals(tag)) {
- minimumPasswordLength = Integer.parseInt(
+ minimumPasswordMetrics.length = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_PASSWORD_HISTORY_LENGTH.equals(tag)) {
passwordHistoryLength = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_UPPERCASE.equals(tag)) {
- minimumPasswordUpperCase = Integer.parseInt(
+ minimumPasswordMetrics.upperCase = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_LOWERCASE.equals(tag)) {
- minimumPasswordLowerCase = Integer.parseInt(
+ minimumPasswordMetrics.lowerCase = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_LETTERS.equals(tag)) {
- minimumPasswordLetters = Integer.parseInt(
+ minimumPasswordMetrics.letters = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_NUMERIC.equals(tag)) {
- minimumPasswordNumeric = Integer.parseInt(
+ minimumPasswordMetrics.numeric = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_SYMBOLS.equals(tag)) {
- minimumPasswordSymbols = Integer.parseInt(
+ minimumPasswordMetrics.symbols = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) {
- minimumPasswordNonLetter = Integer.parseInt(
+ minimumPasswordMetrics.nonLetter = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
maximumTimeToUnlock = Long.parseLong(
@@ -1224,23 +1226,23 @@
}
}
pw.print(prefix); pw.print("passwordQuality=0x");
- pw.println(Integer.toHexString(passwordQuality));
+ pw.println(Integer.toHexString(minimumPasswordMetrics.quality));
pw.print(prefix); pw.print("minimumPasswordLength=");
- pw.println(minimumPasswordLength);
+ pw.println(minimumPasswordMetrics.length);
pw.print(prefix); pw.print("passwordHistoryLength=");
pw.println(passwordHistoryLength);
pw.print(prefix); pw.print("minimumPasswordUpperCase=");
- pw.println(minimumPasswordUpperCase);
+ pw.println(minimumPasswordMetrics.upperCase);
pw.print(prefix); pw.print("minimumPasswordLowerCase=");
- pw.println(minimumPasswordLowerCase);
+ pw.println(minimumPasswordMetrics.lowerCase);
pw.print(prefix); pw.print("minimumPasswordLetters=");
- pw.println(minimumPasswordLetters);
+ pw.println(minimumPasswordMetrics.letters);
pw.print(prefix); pw.print("minimumPasswordNumeric=");
- pw.println(minimumPasswordNumeric);
+ pw.println(minimumPasswordMetrics.numeric);
pw.print(prefix); pw.print("minimumPasswordSymbols=");
- pw.println(minimumPasswordSymbols);
+ pw.println(minimumPasswordMetrics.symbols);
pw.print(prefix); pw.print("minimumPasswordNonLetter=");
- pw.println(minimumPasswordNonLetter);
+ pw.println(minimumPasswordMetrics.nonLetter);
pw.print(prefix); pw.print("maximumTimeToUnlock=");
pw.println(maximumTimeToUnlock);
pw.print(prefix); pw.print("strongAuthUnlockTimeout=");
@@ -2273,20 +2275,17 @@
out.endTag(null, "failed-password-attempts");
}
- if (policy.mActivePasswordQuality != 0 || policy.mActivePasswordLength != 0
- || policy.mActivePasswordUpperCase != 0 || policy.mActivePasswordLowerCase != 0
- || policy.mActivePasswordLetters != 0 || policy.mActivePasswordNumeric != 0
- || policy.mActivePasswordSymbols != 0 || policy.mActivePasswordNonLetter != 0) {
+ final PasswordMetrics metrics = policy.mActivePasswordMetrics;
+ if (!metrics.isDefault()) {
out.startTag(null, "active-password");
- out.attribute(null, "quality", Integer.toString(policy.mActivePasswordQuality));
- out.attribute(null, "length", Integer.toString(policy.mActivePasswordLength));
- out.attribute(null, "uppercase", Integer.toString(policy.mActivePasswordUpperCase));
- out.attribute(null, "lowercase", Integer.toString(policy.mActivePasswordLowerCase));
- out.attribute(null, "letters", Integer.toString(policy.mActivePasswordLetters));
- out.attribute(null, "numeric", Integer
- .toString(policy.mActivePasswordNumeric));
- out.attribute(null, "symbols", Integer.toString(policy.mActivePasswordSymbols));
- out.attribute(null, "nonletter", Integer.toString(policy.mActivePasswordNonLetter));
+ out.attribute(null, "quality", Integer.toString(metrics.quality));
+ out.attribute(null, "length", Integer.toString(metrics.length));
+ out.attribute(null, "uppercase", Integer.toString(metrics.upperCase));
+ out.attribute(null, "lowercase", Integer.toString(metrics.lowerCase));
+ out.attribute(null, "letters", Integer.toString(metrics.letters));
+ out.attribute(null, "numeric", Integer.toString(metrics.numeric));
+ out.attribute(null, "symbols", Integer.toString(metrics.symbols));
+ out.attribute(null, "nonletter", Integer.toString(metrics.nonLetter));
out.endTag(null, "active-password");
}
@@ -2456,22 +2455,15 @@
policy.mPasswordOwner = Integer.parseInt(
parser.getAttributeValue(null, "value"));
} else if ("active-password".equals(tag)) {
- policy.mActivePasswordQuality = Integer.parseInt(
- parser.getAttributeValue(null, "quality"));
- policy.mActivePasswordLength = Integer.parseInt(
- parser.getAttributeValue(null, "length"));
- policy.mActivePasswordUpperCase = Integer.parseInt(
- parser.getAttributeValue(null, "uppercase"));
- policy.mActivePasswordLowerCase = Integer.parseInt(
- parser.getAttributeValue(null, "lowercase"));
- policy.mActivePasswordLetters = Integer.parseInt(
- parser.getAttributeValue(null, "letters"));
- policy.mActivePasswordNumeric = Integer.parseInt(
- parser.getAttributeValue(null, "numeric"));
- policy.mActivePasswordSymbols = Integer.parseInt(
- parser.getAttributeValue(null, "symbols"));
- policy.mActivePasswordNonLetter = Integer.parseInt(
- parser.getAttributeValue(null, "nonletter"));
+ final PasswordMetrics m = policy.mActivePasswordMetrics;
+ m.quality = Integer.parseInt(parser.getAttributeValue(null, "quality"));
+ m.length = Integer.parseInt(parser.getAttributeValue(null, "length"));
+ m.upperCase = Integer.parseInt(parser.getAttributeValue(null, "uppercase"));
+ m.lowerCase = Integer.parseInt(parser.getAttributeValue(null, "lowercase"));
+ m.letters = Integer.parseInt(parser.getAttributeValue(null, "letters"));
+ m.numeric = Integer.parseInt(parser.getAttributeValue(null, "numeric"));
+ m.symbols = Integer.parseInt(parser.getAttributeValue(null, "symbols"));
+ m.nonLetter = Integer.parseInt(parser.getAttributeValue(null, "nonletter"));
} else if (TAG_ACCEPTED_CA_CERTIFICATES.equals(tag)) {
policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME));
} else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) {
@@ -2517,19 +2509,12 @@
final long identity = mInjector.binderClearCallingIdentity();
try {
int actualPasswordQuality = mLockPatternUtils.getActivePasswordQuality(userHandle);
- if (actualPasswordQuality < policy.mActivePasswordQuality) {
+ if (actualPasswordQuality < policy.mActivePasswordMetrics.quality) {
Slog.w(LOG_TAG, "Active password quality 0x"
- + Integer.toHexString(policy.mActivePasswordQuality)
+ + Integer.toHexString(policy.mActivePasswordMetrics.quality)
+ " does not match actual quality 0x"
+ Integer.toHexString(actualPasswordQuality));
- policy.mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
- policy.mActivePasswordLength = 0;
- policy.mActivePasswordUpperCase = 0;
- policy.mActivePasswordLowerCase = 0;
- policy.mActivePasswordLetters = 0;
- policy.mActivePasswordNumeric = 0;
- policy.mActivePasswordSymbols = 0;
- policy.mActivePasswordNonLetter = 0;
+ policy.mActivePasswordMetrics = new PasswordMetrics();
}
} finally {
mInjector.binderRestoreCallingIdentity(identity);
@@ -3119,8 +3104,8 @@
synchronized (this) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
- if (ap.passwordQuality != quality) {
- ap.passwordQuality = quality;
+ if (ap.minimumPasswordMetrics.quality != quality) {
+ ap.minimumPasswordMetrics.quality = quality;
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -3137,7 +3122,7 @@
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
- return admin != null ? admin.passwordQuality : mode;
+ return admin != null ? admin.minimumPasswordMetrics.quality : mode;
}
// Return the strictest policy across all participating admins.
@@ -3146,8 +3131,8 @@
final int N = admins.size();
for (int i = 0; i < N; i++) {
ActiveAdmin admin = admins.get(i);
- if (mode < admin.passwordQuality) {
- mode = admin.passwordQuality;
+ if (mode < admin.minimumPasswordMetrics.quality) {
+ mode = admin.minimumPasswordMetrics.quality;
}
}
return mode;
@@ -3206,8 +3191,8 @@
synchronized (this) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
- if (ap.minimumPasswordLength != length) {
- ap.minimumPasswordLength = length;
+ if (ap.minimumPasswordMetrics.length != length) {
+ ap.minimumPasswordMetrics.length = length;
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -3224,7 +3209,7 @@
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
- return admin != null ? admin.minimumPasswordLength : length;
+ return admin != null ? admin.minimumPasswordMetrics.length : length;
}
// Return the strictest policy across all participating admins.
@@ -3233,8 +3218,8 @@
final int N = admins.size();
for (int i = 0; i < N; i++) {
ActiveAdmin admin = admins.get(i);
- if (length < admin.minimumPasswordLength) {
- length = admin.minimumPasswordLength;
+ if (length < admin.minimumPasswordMetrics.length) {
+ length = admin.minimumPasswordMetrics.length;
}
}
return length;
@@ -3461,8 +3446,8 @@
synchronized (this) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
- if (ap.minimumPasswordUpperCase != length) {
- ap.minimumPasswordUpperCase = length;
+ if (ap.minimumPasswordMetrics.upperCase != length) {
+ ap.minimumPasswordMetrics.upperCase = length;
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -3479,7 +3464,7 @@
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
- return admin != null ? admin.minimumPasswordUpperCase : length;
+ return admin != null ? admin.minimumPasswordMetrics.upperCase : length;
}
// Return the strictest policy across all participating admins.
@@ -3488,8 +3473,8 @@
final int N = admins.size();
for (int i = 0; i < N; i++) {
ActiveAdmin admin = admins.get(i);
- if (length < admin.minimumPasswordUpperCase) {
- length = admin.minimumPasswordUpperCase;
+ if (length < admin.minimumPasswordMetrics.upperCase) {
+ length = admin.minimumPasswordMetrics.upperCase;
}
}
return length;
@@ -3502,8 +3487,8 @@
synchronized (this) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
- if (ap.minimumPasswordLowerCase != length) {
- ap.minimumPasswordLowerCase = length;
+ if (ap.minimumPasswordMetrics.lowerCase != length) {
+ ap.minimumPasswordMetrics.lowerCase = length;
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -3520,7 +3505,7 @@
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
- return admin != null ? admin.minimumPasswordLowerCase : length;
+ return admin != null ? admin.minimumPasswordMetrics.lowerCase : length;
}
// Return the strictest policy across all participating admins.
@@ -3529,8 +3514,8 @@
final int N = admins.size();
for (int i = 0; i < N; i++) {
ActiveAdmin admin = admins.get(i);
- if (length < admin.minimumPasswordLowerCase) {
- length = admin.minimumPasswordLowerCase;
+ if (length < admin.minimumPasswordMetrics.lowerCase) {
+ length = admin.minimumPasswordMetrics.lowerCase;
}
}
return length;
@@ -3546,8 +3531,8 @@
synchronized (this) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
- if (ap.minimumPasswordLetters != length) {
- ap.minimumPasswordLetters = length;
+ if (ap.minimumPasswordMetrics.letters != length) {
+ ap.minimumPasswordMetrics.letters = length;
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -3564,7 +3549,7 @@
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
- return admin != null ? admin.minimumPasswordLetters : length;
+ return admin != null ? admin.minimumPasswordMetrics.letters : length;
}
// Return the strictest policy across all participating admins.
@@ -3576,8 +3561,8 @@
if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) {
continue;
}
- if (length < admin.minimumPasswordLetters) {
- length = admin.minimumPasswordLetters;
+ if (length < admin.minimumPasswordMetrics.letters) {
+ length = admin.minimumPasswordMetrics.letters;
}
}
return length;
@@ -3593,8 +3578,8 @@
synchronized (this) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
- if (ap.minimumPasswordNumeric != length) {
- ap.minimumPasswordNumeric = length;
+ if (ap.minimumPasswordMetrics.numeric != length) {
+ ap.minimumPasswordMetrics.numeric = length;
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -3611,7 +3596,7 @@
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
- return admin != null ? admin.minimumPasswordNumeric : length;
+ return admin != null ? admin.minimumPasswordMetrics.numeric : length;
}
// Return the strictest policy across all participating admins.
@@ -3623,8 +3608,8 @@
if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) {
continue;
}
- if (length < admin.minimumPasswordNumeric) {
- length = admin.minimumPasswordNumeric;
+ if (length < admin.minimumPasswordMetrics.numeric) {
+ length = admin.minimumPasswordMetrics.numeric;
}
}
return length;
@@ -3640,8 +3625,8 @@
synchronized (this) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
- if (ap.minimumPasswordSymbols != length) {
- ap.minimumPasswordSymbols = length;
+ if (ap.minimumPasswordMetrics.symbols != length) {
+ ap.minimumPasswordMetrics.symbols = length;
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -3658,7 +3643,7 @@
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
- return admin != null ? admin.minimumPasswordSymbols : length;
+ return admin != null ? admin.minimumPasswordMetrics.symbols : length;
}
// Return the strictest policy across all participating admins.
@@ -3670,8 +3655,8 @@
if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) {
continue;
}
- if (length < admin.minimumPasswordSymbols) {
- length = admin.minimumPasswordSymbols;
+ if (length < admin.minimumPasswordMetrics.symbols) {
+ length = admin.minimumPasswordMetrics.symbols;
}
}
return length;
@@ -3687,8 +3672,8 @@
synchronized (this) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
- if (ap.minimumPasswordNonLetter != length) {
- ap.minimumPasswordNonLetter = length;
+ if (ap.minimumPasswordMetrics.nonLetter != length) {
+ ap.minimumPasswordMetrics.nonLetter = length;
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -3705,7 +3690,7 @@
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
- return admin != null ? admin.minimumPasswordNonLetter : length;
+ return admin != null ? admin.minimumPasswordMetrics.nonLetter : length;
}
// Return the strictest policy across all participating admins.
@@ -3717,8 +3702,8 @@
if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) {
continue;
}
- if (length < admin.minimumPasswordNonLetter) {
- length = admin.minimumPasswordNonLetter;
+ if (length < admin.minimumPasswordMetrics.nonLetter) {
+ length = admin.minimumPasswordMetrics.nonLetter;
}
}
return length;
@@ -3759,28 +3744,28 @@
private boolean isActivePasswordSufficientForUserLocked(
DevicePolicyData policy, int userHandle, boolean parent) {
final int requiredPasswordQuality = getPasswordQuality(null, userHandle, parent);
- if (policy.mActivePasswordQuality < requiredPasswordQuality) {
+ if (policy.mActivePasswordMetrics.quality < requiredPasswordQuality) {
return false;
}
if (requiredPasswordQuality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
- && policy.mActivePasswordLength < getPasswordMinimumLength(
+ && policy.mActivePasswordMetrics.length < getPasswordMinimumLength(
null, userHandle, parent)) {
return false;
}
if (requiredPasswordQuality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) {
return true;
}
- return policy.mActivePasswordUpperCase >= getPasswordMinimumUpperCase(
+ return policy.mActivePasswordMetrics.upperCase >= getPasswordMinimumUpperCase(
null, userHandle, parent)
- && policy.mActivePasswordLowerCase >= getPasswordMinimumLowerCase(
+ && policy.mActivePasswordMetrics.lowerCase >= getPasswordMinimumLowerCase(
null, userHandle, parent)
- && policy.mActivePasswordLetters >= getPasswordMinimumLetters(
+ && policy.mActivePasswordMetrics.letters >= getPasswordMinimumLetters(
null, userHandle, parent)
- && policy.mActivePasswordNumeric >= getPasswordMinimumNumeric(
+ && policy.mActivePasswordMetrics.numeric >= getPasswordMinimumNumeric(
null, userHandle, parent)
- && policy.mActivePasswordSymbols >= getPasswordMinimumSymbols(
+ && policy.mActivePasswordMetrics.symbols >= getPasswordMinimumSymbols(
null, userHandle, parent)
- && policy.mActivePasswordNonLetter >= getPasswordMinimumNonLetter(
+ && policy.mActivePasswordMetrics.nonLetter >= getPasswordMinimumNonLetter(
null, userHandle, parent);
}
@@ -3974,8 +3959,9 @@
if (quality == DevicePolicyManager.PASSWORD_QUALITY_MANAGED) {
quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
}
+ final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password);
if (quality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
- int realQuality = LockPatternUtils.computePasswordQuality(password);
+ final int realQuality = metrics.quality;
if (realQuality < quality
&& quality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) {
Slog.w(LOG_TAG, "resetPassword: password quality 0x"
@@ -3993,67 +3979,48 @@
return false;
}
if (quality == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) {
- int letters = 0;
- int uppercase = 0;
- int lowercase = 0;
- int numbers = 0;
- int symbols = 0;
- int nonletter = 0;
- for (int i = 0; i < password.length(); i++) {
- char c = password.charAt(i);
- if (c >= 'A' && c <= 'Z') {
- letters++;
- uppercase++;
- } else if (c >= 'a' && c <= 'z') {
- letters++;
- lowercase++;
- } else if (c >= '0' && c <= '9') {
- numbers++;
- nonletter++;
- } else {
- symbols++;
- nonletter++;
- }
- }
int neededLetters = getPasswordMinimumLetters(null, userHandle, /* parent */ false);
- if(letters < neededLetters) {
- Slog.w(LOG_TAG, "resetPassword: number of letters " + letters
+ if(metrics.letters < neededLetters) {
+ Slog.w(LOG_TAG, "resetPassword: number of letters " + metrics.letters
+ " does not meet required number of letters " + neededLetters);
return false;
}
- int neededNumbers = getPasswordMinimumNumeric(null, userHandle, /* parent */ false);
- if (numbers < neededNumbers) {
- Slog.w(LOG_TAG, "resetPassword: number of numerical digits " + numbers
+ int neededNumeric = getPasswordMinimumNumeric(null, userHandle, /* parent */ false);
+ if (metrics.numeric < neededNumeric) {
+ Slog.w(LOG_TAG, "resetPassword: number of numerical digits " + metrics.numeric
+ " does not meet required number of numerical digits "
- + neededNumbers);
+ + neededNumeric);
return false;
}
int neededLowerCase = getPasswordMinimumLowerCase(
null, userHandle, /* parent */ false);
- if (lowercase < neededLowerCase) {
- Slog.w(LOG_TAG, "resetPassword: number of lowercase letters " + lowercase
+ if (metrics.lowerCase < neededLowerCase) {
+ Slog.w(LOG_TAG, "resetPassword: number of lowercase letters "
+ + metrics.lowerCase
+ " does not meet required number of lowercase letters "
+ neededLowerCase);
return false;
}
int neededUpperCase = getPasswordMinimumUpperCase(
null, userHandle, /* parent */ false);
- if (uppercase < neededUpperCase) {
- Slog.w(LOG_TAG, "resetPassword: number of uppercase letters " + uppercase
+ if (metrics.upperCase < neededUpperCase) {
+ Slog.w(LOG_TAG, "resetPassword: number of uppercase letters "
+ + metrics.upperCase
+ " does not meet required number of uppercase letters "
+ neededUpperCase);
return false;
}
int neededSymbols = getPasswordMinimumSymbols(null, userHandle, /* parent */ false);
- if (symbols < neededSymbols) {
- Slog.w(LOG_TAG, "resetPassword: number of special symbols " + symbols
+ if (metrics.symbols < neededSymbols) {
+ Slog.w(LOG_TAG, "resetPassword: number of special symbols " + metrics.symbols
+ " does not meet required number of special symbols " + neededSymbols);
return false;
}
int neededNonLetter = getPasswordMinimumNonLetter(
null, userHandle, /* parent */ false);
- if (nonletter < neededNonLetter) {
- Slog.w(LOG_TAG, "resetPassword: number of non-letter characters " + nonletter
+ if (metrics.nonLetter < neededNonLetter) {
+ Slog.w(LOG_TAG, "resetPassword: number of non-letter characters "
+ + metrics.nonLetter
+ " does not meet required number of non-letter characters "
+ neededNonLetter);
return false;
@@ -4842,8 +4809,7 @@
}
@Override
- public void setActivePasswordState(int quality, int length, int letters, int uppercase,
- int lowercase, int numbers, int symbols, int nonletter, int userHandle) {
+ public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
if (!mHasFeature) {
return;
}
@@ -4856,21 +4822,14 @@
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
- validateQualityConstant(quality);
+ validateQualityConstant(metrics.quality);
DevicePolicyData policy = getUserData(userHandle);
long ident = mInjector.binderClearCallingIdentity();
try {
synchronized (this) {
- policy.mActivePasswordQuality = quality;
- policy.mActivePasswordLength = length;
- policy.mActivePasswordLetters = letters;
- policy.mActivePasswordLowerCase = lowercase;
- policy.mActivePasswordUpperCase = uppercase;
- policy.mActivePasswordNumeric = numbers;
- policy.mActivePasswordSymbols = symbols;
- policy.mActivePasswordNonLetter = nonletter;
+ policy.mActivePasswordMetrics = metrics;
policy.mFailedPasswordAttempts = 0;
saveSettingsLocked(userHandle);
updatePasswordExpirationsLocked(userHandle);
@@ -5891,6 +5850,11 @@
mInjector.binderRestoreCallingIdentity(ident);
}
+ if (isAdb()) {
+ // Log device owner provisioning was started using adb.
+ MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_DEVICE_OWNER);
+ }
+
mOwners.setDeviceOwner(admin, ownerName, userId);
mOwners.writeDeviceOwner();
updateDeviceOwnerLocked();
@@ -6073,6 +6037,11 @@
throw new IllegalArgumentException("Not active admin: " + who);
}
+ if (isAdb()) {
+ // Log profile owner provisioning was started using adb.
+ MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_PROFILE_OWNER);
+ }
+
mOwners.setProfileOwner(who, ownerName, userHandle);
mOwners.writeProfileOwner(userHandle);
Slog.i(LOG_TAG, "Profile owner set: " + who + " on user " + userHandle);
@@ -6206,8 +6175,7 @@
boolean transitionCheckNeeded = true;
// Calling identity/permission checks.
- final int callingUid = mInjector.binderGetCallingUid();
- if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
+ if (isAdb()) {
// ADB shell can only move directly from un-managed to finalized as part of directly
// setting profile-owner or device-owner.
if (getUserProvisioningState(userHandle) !=
@@ -6410,8 +6378,7 @@
throw new IllegalStateException("Trying to set the profile owner, but the user "
+ "already has a device owner.");
}
- int callingUid = mInjector.binderGetCallingUid();
- if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
+ if (isAdb()) {
if (hasUserSetupCompleted(userHandle)
&& hasIncompatibleAccountsLocked(userHandle, owner)) {
throw new IllegalStateException("Not allowed to set the profile owner because "
@@ -6431,13 +6398,11 @@
* permission.
*/
private void enforceCanSetDeviceOwnerLocked(@Nullable ComponentName owner, int userId) {
- int callingUid = mInjector.binderGetCallingUid();
- boolean isAdb = callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
- if (!isAdb) {
+ if (!isAdb()) {
enforceCanManageProfileAndDeviceOwners();
}
- final int code = checkSetDeviceOwnerPreConditionLocked(owner, userId, isAdb);
+ final int code = checkSetDeviceOwnerPreConditionLocked(owner, userId, isAdb());
switch (code) {
case CODE_OK:
return;
@@ -8423,10 +8388,10 @@
/**
* Returns true if specified admin is allowed to limit passwords and has a
- * {@code passwordQuality} of at least {@code minPasswordQuality}
+ * {@code minimumPasswordMetrics.quality} of at least {@code minPasswordQuality}
*/
private static boolean isLimitPasswordAllowed(ActiveAdmin admin, int minPasswordQuality) {
- if (admin.passwordQuality < minPasswordQuality) {
+ if (admin.minimumPasswordMetrics.quality < minPasswordQuality) {
return false;
}
return admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
@@ -9483,4 +9448,9 @@
return false;
}
}
+
+ private boolean isAdb() {
+ final int callingUid = mInjector.binderGetCallingUid();
+ return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 85b0d96..62947eb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -118,6 +118,8 @@
import java.util.Timer;
import java.util.TimerTask;
+import static android.view.Display.DEFAULT_DISPLAY;
+
public final class SystemServer {
private static final String TAG = "SystemServer";
@@ -1403,7 +1405,7 @@
// Update the configuration for this context by hand, because we're going
// to start using it before the config change done in wm.systemReady() will
// propagate to it.
- Configuration config = wm.computeNewConfiguration();
+ final Configuration config = wm.computeNewConfiguration(DEFAULT_DISPLAY);
DisplayMetrics metrics = new DisplayMetrics();
WindowManager w = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
w.getDefaultDisplay().getMetrics(metrics);
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index ffbea9f..8dd05b1 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -40,6 +40,7 @@
import android.system.ErrnoException;
import android.system.Os;
import android.system.PacketSocketAddress;
+import android.util.EventLog;
import android.util.Log;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -369,6 +370,13 @@
if (PACKET_DBG) {
Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length));
}
+ if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) {
+ int snetTagId = 0x534e4554;
+ String bugId = "31850211";
+ int uid = -1;
+ String data = DhcpPacket.ParseException.class.getName();
+ EventLog.writeEvent(snetTagId, bugId, uid, data);
+ }
logError(e.errorCode);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 0f180af..a921e8a 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -236,6 +236,7 @@
private final IdleableHandlerThread mHandlerThread;
private final ConditionVariable mDisconnected = new ConditionVariable();
private final ConditionVariable mNetworkStatusReceived = new ConditionVariable();
+ private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
private int mScore;
private NetworkAgent mNetworkAgent;
private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
@@ -291,6 +292,11 @@
mRedirectUrl = redirectUrl;
mNetworkStatusReceived.open();
}
+
+ @Override
+ protected void preventAutomaticReconnect() {
+ mPreventReconnectReceived.open();
+ }
};
// Waits for the NetworkAgent to be registered, which includes the creation of the
// NetworkMonitor.
@@ -375,11 +381,6 @@
mWrappedNetworkMonitor.gen204ProbeResult = 200;
mWrappedNetworkMonitor.gen204ProbeRedirectUrl = redirectUrl;
connect(false);
- waitFor(new Criteria() { public boolean get() {
- NetworkCapabilities caps = mCm.getNetworkCapabilities(getNetwork());
- return caps != null && caps.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL);} });
- mWrappedNetworkMonitor.gen204ProbeResult = 500;
- mWrappedNetworkMonitor.gen204ProbeRedirectUrl = null;
}
public void disconnect() {
@@ -391,6 +392,10 @@
return new Network(mNetworkAgent.netId);
}
+ public ConditionVariable getPreventReconnectReceived() {
+ return mPreventReconnectReceived;
+ }
+
public ConditionVariable getDisconnectedCV() {
return mDisconnected;
}
@@ -597,6 +602,7 @@
@Override
protected CaptivePortalProbeResult isCaptivePortal() {
+ if (!mIsCaptivePortalCheckEnabled) { return new CaptivePortalProbeResult(204); }
return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl, null);
}
}
@@ -743,6 +749,9 @@
mService.systemReady();
mCm = new WrappedConnectivityManager(getContext(), mService);
mCm.bindProcessToNetwork(null);
+
+ // Ensure that the default setting for Captive Portals is used for most tests
+ setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
}
public void tearDown() throws Exception {
@@ -1710,6 +1719,47 @@
validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
}
+ @LargeTest
+ public void testAvoidOrIgnoreCaptivePortals() {
+ final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
+ final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
+ mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
+
+ final TestNetworkCallback validatedCallback = new TestNetworkCallback();
+ final NetworkRequest validatedRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_VALIDATED).build();
+ mCm.registerNetworkCallback(validatedRequest, validatedCallback);
+
+ setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_AVOID);
+ // Bring up a network with a captive portal.
+ // Expect it to fail to connect and not result in any callbacks.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ String firstRedirectUrl = "http://example.com/firstPath";
+
+ ConditionVariable disconnectCv = mWiFiNetworkAgent.getDisconnectedCV();
+ ConditionVariable avoidCv = mWiFiNetworkAgent.getPreventReconnectReceived();
+ mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
+ waitFor(disconnectCv);
+ waitFor(avoidCv);
+
+ assertNoCallbacks(captivePortalCallback, validatedCallback);
+
+ // Now test ignore mode.
+ setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
+
+ // Bring up a network with a captive portal.
+ // Since we're ignoring captive portals, the network will validate.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ String secondRedirectUrl = "http://example.com/secondPath";
+ mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
+
+ // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
+ validatedCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ // But there should be no CaptivePortal callback.
+ captivePortalCallback.assertNoCallback();
+ }
+
@SmallTest
public void testInvalidNetworkSpecifier() {
boolean execptionCalled = true;
@@ -1850,6 +1900,11 @@
mCm.unregisterNetworkCallback(cellNetworkCallback);
}
+ private void setCaptivePortalMode(int mode) {
+ ContentResolver cr = mServiceContext.getContentResolver();
+ Settings.Global.putInt(cr, Settings.Global.CAPTIVE_PORTAL_MODE, mode);
+ }
+
private void setMobileDataAlwaysOn(boolean enable) {
ContentResolver cr = mServiceContext.getContentResolver();
Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, enable ? 1 : 0);
diff --git a/services/tests/servicestests/src/com/android/server/am/ConfigurationContainerTests.java b/services/tests/servicestests/src/com/android/server/am/ConfigurationContainerTests.java
index 92c442e..fd2fb4b 100644
--- a/services/tests/servicestests/src/com/android/server/am/ConfigurationContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ConfigurationContainerTests.java
@@ -97,22 +97,30 @@
final Configuration childOverrideConfig = new Configuration();
childOverrideConfig.densityDpi = 320;
child.onOverrideConfigurationChanged(childOverrideConfig);
+ final Configuration mergedOverrideConfig = new Configuration(root.getConfiguration());
+ mergedOverrideConfig.updateFrom(childOverrideConfig);
// Check configuration update when child is removed from parent.
root.removeChild(child);
assertEquals(childOverrideConfig, child.getOverrideConfiguration());
- assertEquals(childOverrideConfig, child.getMergedOverrideConfiguration());
- assertEquals(childOverrideConfig, child.getConfiguration());
+ assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration());
+ assertEquals(mergedOverrideConfig, child.getConfiguration());
// It may be paranoia... but let's check if parent's config didn't change after removal.
assertEquals(rootOverrideConfig, root.getOverrideConfiguration());
assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
assertEquals(rootOverrideConfig, root.getConfiguration());
- // Check configuration update when child is added to parent.
- final Configuration mergedOverrideConfig = new Configuration(root.getConfiguration());
+ // Init different root
+ final TestConfigurationContainer root2 = new TestConfigurationContainer();
+ final Configuration rootOverrideConfig2 = new Configuration();
+ rootOverrideConfig2.fontScale = 1.1f;
+ root2.onOverrideConfigurationChanged(rootOverrideConfig2);
+
+ // Check configuration update when child is added to different parent.
+ mergedOverrideConfig.setTo(rootOverrideConfig2);
mergedOverrideConfig.updateFrom(childOverrideConfig);
- root.addChild(child);
+ root2.addChild(child);
assertEquals(childOverrideConfig, child.getOverrideConfiguration());
assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration());
assertEquals(mergedOverrideConfig, child.getConfiguration());
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index aed3635..84f0f90 100644
--- a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -71,7 +71,8 @@
" transport_types: 3",
" >",
" time_ms: 1",
- ">");
+ ">",
+ "version: 2");
verifySerialization(want, ev);
}
@@ -93,7 +94,8 @@
" state_transition: \"SomeState\"",
" >",
" time_ms: 1",
- ">");
+ ">",
+ "version: 2");
verifySerialization(want, ev);
}
@@ -114,7 +116,8 @@
" state_transition: \"\"",
" >",
" time_ms: 1",
- ">");
+ ">",
+ "version: 2");
verifySerialization(want, ev);
}
@@ -160,7 +163,8 @@
" return_codes: 178",
" >",
" time_ms: 1",
- ">");
+ ">",
+ "version: 2");
verifySerialization(want, ev);
}
@@ -181,7 +185,8 @@
" latency_ms: 5678",
" >",
" time_ms: 1",
- ">");
+ ">",
+ "version: 2");
verifySerialization(want, ev);
}
@@ -200,7 +205,8 @@
" if_name: \"wlan0\"",
" >",
" time_ms: 1",
- ">");
+ ">",
+ "version: 2");
verifySerialization(want, ev);
}
@@ -223,7 +229,8 @@
" >",
" >",
" time_ms: 1",
- ">");
+ ">",
+ "version: 2");
verifySerialization(want, ev);
}
@@ -248,7 +255,8 @@
" probe_result: 204",
" probe_type: 1",
" >",
- ">");
+ ">",
+ "version: 2");
verifySerialization(want, ev);
}
@@ -274,7 +282,8 @@
" program_length: 2048",
" >",
" time_ms: 1",
- ">");
+ ">",
+ "version: 2");
verifySerialization(want, ev);
}
@@ -305,7 +314,8 @@
" zero_lifetime_ras: 1",
" >",
" time_ms: 1",
- ">");
+ ">",
+ "version: 2");
verifySerialization(want, ev);
}
@@ -332,7 +342,8 @@
" router_lifetime: 2000",
" >",
" time_ms: 1",
- ">");
+ ">",
+ "version: 2");
verifySerialization(want, ev);
}
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java b/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 3fc89b9..aa491bb 100644
--- a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics;
+import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
import android.net.metrics.DefaultNetworkEvent;
import android.net.metrics.DhcpClientEvent;
@@ -57,7 +58,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mService = new IpConnectivityMetrics(mCtx);
+ mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000);
}
public void testLoggingEvents() throws Exception {
@@ -112,6 +113,27 @@
assertEquals("", output3);
}
+ public void testRateLimiting() {
+ final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
+ final ApfProgramEvent ev = new ApfProgramEvent(0, 0, 0, 0, 0);
+ final long fakeTimestamp = 1;
+
+ int attempt = 100; // More than burst quota, but less than buffer size.
+ for (int i = 0; i < attempt; i++) {
+ logger.log(ev);
+ }
+
+ String output1 = getdump("flush");
+ assertFalse("".equals(output1));
+
+ for (int i = 0; i < attempt; i++) {
+ assertFalse("expected event to be dropped", logger.log(fakeTimestamp, ev));
+ }
+
+ String output2 = getdump("flush");
+ assertEquals("", output2);
+ }
+
public void testEndToEndLogging() {
IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
@@ -204,7 +226,8 @@
" router_lifetime: 2000",
" >",
" time_ms: 700",
- ">");
+ ">",
+ "version: 2");
verifySerialization(want, getdump("flush"));
}
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/TetheringTest.java b/services/tests/servicestests/src/com/android/server/connectivity/TetheringTest.java
new file mode 100644
index 0000000..a9f68c8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/connectivity/TetheringTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2016 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.server.connectivity;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
+import android.os.INetworkManagementService;
+import android.os.PersistableBundle;
+import android.os.test.TestLooper;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.CarrierConfigManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TetheringTest {
+ private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
+
+ @Mock private Context mContext;
+ @Mock private INetworkManagementService mNMService;
+ @Mock private INetworkStatsService mStatsService;
+ @Mock private INetworkPolicyManager mPolicyManager;
+ @Mock private MockableSystemProperties mSystemProperties;
+ @Mock private Resources mResources;
+ @Mock private CarrierConfigManager mCarrierConfigManager;
+
+ // Like so many Android system APIs, these cannot be mocked because it is marked final.
+ // We have to use the real versions.
+ private final PersistableBundle mCarrierConfig = new PersistableBundle();
+ private final TestLooper mLooper = new TestLooper();
+
+ private Tethering mTethering;
+
+ @Before public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range))
+ .thenReturn(new String[0]);
+ when(mResources.getStringArray(com.android.internal.R.array.config_tether_usb_regexs))
+ .thenReturn(new String[0]);
+ when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs))
+ .thenReturn(new String[0]);
+ when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs))
+ .thenReturn(new String[0]);
+ when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
+ .thenReturn(new int[0]);
+ mTethering = new Tethering(mContext, mNMService, mStatsService, mPolicyManager,
+ mLooper.getLooper(), mSystemProperties);
+ }
+
+ private void setupForRequiredProvisioning() {
+ // Produce some acceptable looking provision app setting if requested.
+ when(mResources.getStringArray(
+ com.android.internal.R.array.config_mobile_hotspot_provision_app))
+ .thenReturn(PROVISIONING_APP_NAME);
+ // Don't disable tethering provisioning unless requested.
+ when(mSystemProperties.getBoolean(eq(Tethering.DISABLE_PROVISIONING_SYSPROP_KEY),
+ anyBoolean())).thenReturn(false);
+ // Act like the CarrierConfigManager is present and ready unless told otherwise.
+ when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
+ .thenReturn(mCarrierConfigManager);
+ when(mCarrierConfigManager.getConfig()).thenReturn(mCarrierConfig);
+ mCarrierConfig.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true);
+ }
+
+ @Test
+ public void canRequireProvisioning() {
+ setupForRequiredProvisioning();
+ assertTrue(mTethering.isTetherProvisioningRequired());
+ }
+
+ @Test
+ public void toleratesCarrierConfigManagerMissing() {
+ setupForRequiredProvisioning();
+ when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
+ .thenReturn(null);
+ // Couldn't get the CarrierConfigManager, but still had a declared provisioning app.
+ // We therefore still require provisioning.
+ assertTrue(mTethering.isTetherProvisioningRequired());
+ }
+
+ @Test
+ public void toleratesCarrierConfigMissing() {
+ setupForRequiredProvisioning();
+ when(mCarrierConfigManager.getConfig()).thenReturn(null);
+ // We still have a provisioning app configured, so still require provisioning.
+ assertTrue(mTethering.isTetherProvisioningRequired());
+ }
+
+ @Test
+ public void provisioningNotRequiredWhenAppNotFound() {
+ setupForRequiredProvisioning();
+ when(mResources.getStringArray(
+ com.android.internal.R.array.config_mobile_hotspot_provision_app))
+ .thenReturn(null);
+ assertTrue(!mTethering.isTetherProvisioningRequired());
+ when(mResources.getStringArray(
+ com.android.internal.R.array.config_mobile_hotspot_provision_app))
+ .thenReturn(new String[] {"malformedApp"});
+ assertTrue(!mTethering.isTetherProvisioningRequired());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/servicestests/src/com/android/server/notification/GroupHelperTest.java
new file mode 100644
index 0000000..22b674b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2016 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.server.notification;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GroupHelperTest {
+ private @Mock GroupHelper.Callback mCallback;
+
+ private GroupHelper mGroupHelper;
+
+ private Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mGroupHelper = new GroupHelper(mCallback);
+ }
+
+ private StatusBarNotification getSbn(String pkg, int id, String tag,
+ UserHandle user, String groupKey) {
+ Notification.Builder nb = new Notification.Builder(getContext())
+ .setContentTitle("A")
+ .setWhen(1205);
+ if (groupKey != null) {
+ nb.setGroup(groupKey);
+ }
+ return new StatusBarNotification(pkg, pkg, id, tag, 0, 0, 0, nb.build(), user);
+ }
+
+ private StatusBarNotification getSbn(String pkg, int id, String tag,
+ UserHandle user) {
+ return getSbn(pkg, id, tag, user, null);
+ }
+
+ @Test
+ public void testNoGroup_postingUnderLimit() throws Exception {
+ final String pkg = "package";
+ for (int i = 0; i < GroupHelper.AUTOGROUP_AT_COUNT - 1; i++) {
+ mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ }
+ verify(mCallback, never()).addAutoGroupSummary(
+ eq(UserHandle.USER_SYSTEM), eq(pkg), anyString());
+ verify(mCallback, never()).addAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ }
+
+ @Test
+ public void testNoGroup_multiPackage() throws Exception {
+ final String pkg = "package";
+ final String pkg2 = "package2";
+ for (int i = 0; i < GroupHelper.AUTOGROUP_AT_COUNT - 1; i++) {
+ mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ }
+ mGroupHelper.onNotificationPosted(
+ getSbn(pkg2, GroupHelper.AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM));
+ verify(mCallback, never()).addAutoGroupSummary(
+ eq(UserHandle.USER_SYSTEM), eq(pkg), anyString());
+ verify(mCallback, never()).addAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ }
+
+ @Test
+ public void testNoGroup_multiUser() throws Exception {
+ final String pkg = "package";
+ for (int i = 0; i < GroupHelper.AUTOGROUP_AT_COUNT - 1; i++) {
+ mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ }
+ mGroupHelper.onNotificationPosted(
+ getSbn(pkg, GroupHelper.AUTOGROUP_AT_COUNT, "four", UserHandle.ALL));
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), eq(pkg), anyString());
+ verify(mCallback, never()).addAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ }
+
+ @Test
+ public void testNoGroup_someAreGrouped() throws Exception {
+ final String pkg = "package";
+ for (int i = 0; i < GroupHelper.AUTOGROUP_AT_COUNT - 1; i++) {
+ mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ }
+ mGroupHelper.onNotificationPosted(
+ getSbn(pkg, GroupHelper.AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM, "a"));
+ verify(mCallback, never()).addAutoGroupSummary(
+ eq(UserHandle.USER_SYSTEM), eq(pkg), anyString());
+ verify(mCallback, never()).addAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ }
+
+
+ @Test
+ public void testPostingOverLimit() throws Exception {
+ final String pkg = "package";
+ for (int i = 0; i < GroupHelper.AUTOGROUP_AT_COUNT; i++) {
+ mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString());
+ verify(mCallback, times(GroupHelper.AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ }
+
+ @Test
+ public void testDropBelowLimitRemoveGroup() throws Exception {
+ final String pkg = "package";
+ List<StatusBarNotification> posted = new ArrayList<>();
+ for (int i = 0; i < GroupHelper.AUTOGROUP_AT_COUNT; i++) {
+ final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ posted.add(sbn);
+ mGroupHelper.onNotificationPosted(sbn);
+ }
+ mGroupHelper.onNotificationRemoved(posted.remove(0));
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString());
+ verify(mCallback, times(GroupHelper.AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, times(GroupHelper.AUTOGROUP_AT_COUNT - 1)).removeAutoGroup(anyString());
+ verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index b5eb77c..763c50b 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -118,4 +118,7 @@
}
throw new NameNotFoundException();
}
+
+ @Override
+ public void setMultiprocessEnabled(boolean enabled) {}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index fee4783..03cbb43 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import com.android.internal.policy.IShortcutService;
+import com.android.server.input.InputManagerService;
import android.content.Context;
import android.content.res.CompatibilityInfo;
@@ -79,7 +80,9 @@
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-public class TestWindowManagerPolicy implements WindowManagerPolicy {
+import static org.mockito.Mockito.mock;
+
+class TestWindowManagerPolicy implements WindowManagerPolicy {
private static final String TAG = "TestWindowManagerPolicy";
private static WindowManagerService sWm = null;
@@ -88,8 +91,8 @@
if (sWm == null) {
// We only want to do this once for the test process as we don't want WM to try to
// register a bunch of local services again.
- sWm = WindowManagerService.main(
- context, null, true, false, false, new TestWindowManagerPolicy());
+ sWm = WindowManagerService.main(context, mock(InputManagerService.class), true, false,
+ false, new TestWindowManagerPolicy());
}
return sWm;
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index 6eb347b..128317c 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -434,22 +434,30 @@
final Configuration childOverrideConfig = new Configuration();
childOverrideConfig.densityDpi = 320;
child.onOverrideConfigurationChanged(childOverrideConfig);
+ final Configuration mergedOverrideConfig = new Configuration(root.getConfiguration());
+ mergedOverrideConfig.updateFrom(childOverrideConfig);
- // Check configuration update when child is removed from parent.
+ // Check configuration update when child is removed from parent - it should remain same.
root.removeChild(child);
assertEquals(childOverrideConfig, child.getOverrideConfiguration());
- assertEquals(childOverrideConfig, child.getMergedOverrideConfiguration());
- assertEquals(childOverrideConfig, child.getConfiguration());
+ assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration());
+ assertEquals(mergedOverrideConfig, child.getConfiguration());
// It may be paranoia... but let's check if parent's config didn't change after removal.
assertEquals(rootOverrideConfig, root.getOverrideConfiguration());
assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
assertEquals(rootOverrideConfig, root.getConfiguration());
- // Check configuration update when child is added to parent.
- final Configuration mergedOverrideConfig = new Configuration(root.getConfiguration());
+ // Init different root
+ final TestWindowContainer root2 = builder.setLayer(0).build();
+ final Configuration rootOverrideConfig2 = new Configuration();
+ rootOverrideConfig2.fontScale = 1.1f;
+ root2.onOverrideConfigurationChanged(rootOverrideConfig2);
+
+ // Check configuration update when child is added to different parent.
+ mergedOverrideConfig.setTo(rootOverrideConfig2);
mergedOverrideConfig.updateFrom(childOverrideConfig);
- root.addChildWindow(child);
+ root2.addChildWindow(child);
assertEquals(childOverrideConfig, child.getOverrideConfiguration());
assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration());
assertEquals(mergedOverrideConfig, child.getConfiguration());
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
index 1a4dff9..546c7da 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
@@ -21,6 +21,7 @@
import org.junit.runner.RunWith;
import android.content.Context;
+import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -28,12 +29,14 @@
import android.view.IWindow;
import android.view.WindowManager;
+import static android.app.AppOpsManager.OP_NONE;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
/**
* Tests for the {@link WindowState} class.
@@ -49,6 +52,7 @@
private WindowManagerService mWm = null;
private final IWindow mIWindow = new TestIWindow();
+ private final Session mMockSession = mock(Session.class);
@Before
public void setUp() throws Exception {
@@ -86,6 +90,28 @@
}
@Test
+ public void testChildRemoval() throws Exception {
+ final TestWindowToken token = new TestWindowToken();
+ final DisplayContent dc = mWm.getDefaultDisplayContentLocked();
+
+ assertEquals(token, dc.getWindowToken(token.token));
+
+ final WindowState window1 = createWindow(null, TYPE_APPLICATION, token);
+ final WindowState window2 = createWindow(null, TYPE_APPLICATION, token);
+ token.addWindow(window1);
+ token.addWindow(window2);
+
+ window2.removeImmediately();
+ // The token should still be mapped in the display content since it still has a child.
+ assertEquals(token, dc.getWindowToken(token.token));
+
+ window1.removeImmediately();
+ // The token should have been removed from the display content since it no longer has a
+ // child.
+ assertEquals(null, dc.getWindowToken(token.token));
+ }
+
+ @Test
public void testAdjustAnimLayer() throws Exception {
final TestWindowToken token = new TestWindowToken();
final WindowState window1 = createWindow(null, TYPE_APPLICATION, token);
@@ -157,14 +183,14 @@
private WindowState createWindow(WindowState parent, int type, WindowToken token) {
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
- return new WindowState(mWm, null, mIWindow, token, parent, 0, 0, attrs, 0, 0);
+ return new WindowState(mWm, mMockSession, mIWindow, token, parent, OP_NONE, 0, attrs, 0, 0);
}
/* Used so we can gain access to some protected members of the {@link WindowToken} class */
private class TestWindowToken extends WindowToken {
TestWindowToken() {
- super(mWm, null, 0, false, mWm.getDefaultDisplayContentLocked());
+ super(mWm, mock(IBinder.class), 0, false, mWm.getDefaultDisplayContentLocked());
}
int getWindowsCount() {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index c3075b3..e58fe70 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -570,7 +570,9 @@
@Override
public void onServiceDisconnected(ComponentName name) {
mCallback.sessionConnectionGone(this);
- mService = null;
+ synchronized (mLock) {
+ mService = null;
+ }
}
public void dump(String prefix, PrintWriter pw) {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index c006185..8f9c758 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -420,6 +420,31 @@
"android.telecom.extra.DISABLE_ADD_CALL";
/**
+ * String connection extra key on a {@link Connection} or {@link Conference} which contains the
+ * original Connection ID associated with the connection. Used in
+ * {@link RemoteConnectionService} to track the Connection ID which was originally assigned to a
+ * connection/conference added via
+ * {@link ConnectionService#addExistingConnection(PhoneAccountHandle, Connection)} and
+ * {@link ConnectionService#addConference(Conference)} APIs. This is important to pass to
+ * Telecom for when it deals with RemoteConnections. When the ConnectionManager wraps the
+ * {@link RemoteConnection} and {@link RemoteConference} and adds it to Telecom, there needs to
+ * be a way to ensure that we don't add the connection again as a duplicate.
+ * <p>
+ * For example, the TelephonyCS calls addExistingConnection for a Connection with ID
+ * {@code TelephonyCS@1}. The ConnectionManager learns of this via
+ * {@link ConnectionService#onRemoteExistingConnectionAdded(RemoteConnection)}, and wraps this
+ * in a new {@link Connection} which it adds to Telecom via
+ * {@link ConnectionService#addExistingConnection(PhoneAccountHandle, Connection)}. As part of
+ * this process, the wrapped RemoteConnection gets assigned a new ID (e.g. {@code ConnMan@1}).
+ * The TelephonyCS will ALSO try to add the existing connection to Telecom, except with the
+ * ID it originally referred to the connection as. Thus Telecom needs to know that the
+ * Connection with ID {@code ConnMan@1} is really the same as {@code TelephonyCS@1}.
+ * @hide
+ */
+ public static final String EXTRA_ORIGINAL_CONNECTION_ID =
+ "android.telecom.extra.ORIGINAL_CONNECTION_ID";
+
+ /**
* Connection event used to inform Telecom that it should play the on hold tone. This is used
* to play a tone when the peer puts the current call on hold. Sent to Telecom via
* {@link #sendConnectionEvent(String, Bundle)}.
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 7b68a4c..19388e99 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1347,7 +1347,13 @@
*/
private String addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection) {
String id;
- if (handle == null) {
+
+ if (connection.getExtras() != null && connection.getExtras()
+ .containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
+ id = connection.getExtras().getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID);
+ Log.d(this, "addExistingConnectionInternal - conn %s reusing original id %s",
+ connection.getTelecomCallId(), id);
+ } else if (handle == null) {
// If no phone account handle was provided, we cannot be sure the call ID is unique,
// so just use a random UUID.
id = UUID.randomUUID().toString();
@@ -1381,13 +1387,21 @@
}
private String addConferenceInternal(Conference conference) {
+ String originalId = null;
+ if (conference.getExtras() != null && conference.getExtras()
+ .containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
+ originalId = conference.getExtras().getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID);
+ Log.d(this, "addConferenceInternal: conf %s reusing original id %s",
+ conference.getTelecomCallId(),
+ originalId);
+ }
if (mIdByConference.containsKey(conference)) {
Log.w(this, "Re-adding an existing conference: %s.", conference);
} else if (conference != null) {
// Conferences do not (yet) have a PhoneAccountHandle associated with them, so we
// cannot determine a ConnectionService class name to associate with the ID, so use
// a unique UUID (for now).
- String id = UUID.randomUUID().toString();
+ String id = originalId == null ? UUID.randomUUID().toString() : originalId;
mConferenceById.put(id, conference);
mIdByConference.put(conference, id);
conference.addListener(mConferenceListener);
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index ecda3cd..249d32b 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
import android.telecom.Logging.EventManager;
@@ -175,6 +176,10 @@
* loggers.
*/
+ public static void setSessionContext(Context context) {
+ getSessionManager().setContext(context);
+ }
+
public static void startSession(String shortMethodName) {
getSessionManager().startSession(shortMethodName, null);
}
@@ -199,6 +204,10 @@
getSessionManager().endSession();
}
+ public static void registerSessionListener(SessionManager.ISessionListener l) {
+ getSessionManager().registerSessionListener(l);
+ }
+
public static String getSessionId() {
// If the Session logger has not been initialized, then there have been no sessions logged.
// Don't load it now!
@@ -300,7 +309,7 @@
private static MessageDigest sMessageDigest;
- static void initMd5Sum() {
+ public static void initMd5Sum() {
new AsyncTask<Void, Void, Void>() {
@Override
public Void doInBackground(Void... args) {
@@ -426,7 +435,7 @@
msg = (args == null || args.length == 0) ? format
: String.format(Locale.US, format, args);
} catch (IllegalFormatException ife) {
- e("Log", ife, "IllegalFormatException: formatString='%s' numArgs=%d", format,
+ e(TAG, ife, "Log: IllegalFormatException: formatString='%s' numArgs=%d", format,
args.length);
msg = format + " (An error occurred while formatting the message.)";
}
diff --git a/telecomm/java/android/telecom/Logging/EventManager.java b/telecomm/java/android/telecom/Logging/EventManager.java
index 0849804..2cd1b96 100644
--- a/telecomm/java/android/telecom/Logging/EventManager.java
+++ b/telecomm/java/android/telecom/Logging/EventManager.java
@@ -291,7 +291,7 @@
msg = (args == null || args.length == 0) ? format
: String.format(Locale.US, format, args);
} catch (IllegalFormatException ife) {
- Log.e("Log", ife, "IllegalFormatException: formatString='%s' numArgs=%d", format,
+ Log.e(this, ife, "IllegalFormatException: formatString='%s' numArgs=%d", format,
args.length);
msg = format + " (An error occurred while formatting the message.)";
}
@@ -366,8 +366,6 @@
// Now add a new entry
mEventRecords.add(newRecord);
mCallEventRecordMap.put(recordEntry, newRecord);
-
- // TODO: Add Implementation of this in Telecom for Analytics
synchronized (mSync) {
for (EventListener l : mEventListeners) {
l.eventRecordAdded(newRecord);
diff --git a/telecomm/java/android/telecom/Logging/Runnable.java b/telecomm/java/android/telecom/Logging/Runnable.java
new file mode 100644
index 0000000..b2cf3a3
--- /dev/null
+++ b/telecomm/java/android/telecom/Logging/Runnable.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 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 android.telecom.Logging;
+
+import android.telecom.Log;
+
+/**
+ * Encapsulates session logging in a Runnable to reduce code duplication when continuing subsessions
+ * in a handler/thread.
+ * @hide
+ */
+public abstract class Runnable {
+
+ private Session mSubsession;
+ private final String mSubsessionName;
+ private final Object mLock;
+ private final java.lang.Runnable mRunnable = new java.lang.Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ try {
+ Log.continueSession(mSubsession, mSubsessionName);
+ loggedRun();
+ } finally {
+ if (mSubsession != null) {
+ Log.endSession();
+ mSubsession = null;
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * Creates a new Telecom Runnable that incorporates Session Logging into it. Useful for carrying
+ * Logging Sessions through different threads as well as through handlers.
+ * @param subsessionName The name that will be used in the Logs to mark this Session
+ * @param lock The synchronization lock that will be used to lock loggedRun().
+ */
+ public Runnable(String subsessionName, Object lock) {
+ if (lock == null) {
+ mLock = new Object();
+ } else {
+ mLock = lock;
+ }
+ mSubsessionName = subsessionName;
+ }
+
+ /**
+ * Return the runnable that will be canceled in the handler queue.
+ * @return Runnable object to cancel.
+ */
+ public final java.lang.Runnable getRunnableToCancel() {
+ return mRunnable;
+ }
+
+ /**
+ * Creates a Runnable and a logging subsession that can be used in a handler/thread. Be sure to
+ * call cancel() if this session is never going to be run (removed from a handler queue, for
+ * for example).
+ * @return A Java Runnable that can be used in a handler queue or thread.
+ */
+ public java.lang.Runnable prepare() {
+ cancel();
+ mSubsession = Log.createSubsession();
+ return mRunnable;
+ }
+
+ /**
+ * This method is used to clean up the active session if the Runnable gets removed from a
+ * handler and is never run.
+ */
+ public void cancel() {
+ synchronized (mLock) {
+ Log.cancelSubsession(mSubsession);
+ mSubsession = null;
+ }
+ }
+
+ /**
+ * The method that will be run in the handler/thread.
+ */
+ abstract public void loggedRun();
+
+}
diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java
index 90daee0..add1237 100644
--- a/telecomm/java/android/telecom/Logging/SessionManager.java
+++ b/telecomm/java/android/telecom/Logging/SessionManager.java
@@ -273,7 +273,6 @@
System.currentTimeMillis() - subsession.getExecutionStartTimeMilliseconds();
android.util.Slog.d(LOGGING_TAG, Session.END_SESSION + " (dur: " + fullSessionTimeMs
+ " ms): " + subsession.toString());
- // TODO: Add analytics hook
for (ISessionListener l : mSessionListeners) {
l.sessionComplete(subsession.getShortMethodName(), fullSessionTimeMs);
}
diff --git a/telecomm/java/android/telecom/RemoteConference.java b/telecomm/java/android/telecom/RemoteConference.java
index 943da6d..0ef9ec1 100644
--- a/telecomm/java/android/telecom/RemoteConference.java
+++ b/telecomm/java/android/telecom/RemoteConference.java
@@ -311,6 +311,9 @@
/** @hide */
void putExtras(final Bundle extras) {
+ if (extras == null) {
+ return;
+ }
if (mExtras == null) {
mExtras = new Bundle();
}
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index f030115..37fa374 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -651,6 +651,14 @@
mCallerDisplayName = connection.getCallerDisplayName();
mCallerDisplayNamePresentation = connection.getCallerDisplayNamePresentation();
mConference = null;
+ putExtras(connection.getExtras());
+
+ // Stash the original connection ID as it exists in the source ConnectionService.
+ // Telecom will use this to avoid adding duplicates later.
+ // See comments on Connection.EXTRA_ORIGINAL_CONNECTION_ID for more information.
+ Bundle newExtras = new Bundle();
+ newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
+ putExtras(newExtras);
}
/**
@@ -1348,6 +1356,9 @@
/** @hide */
void putExtras(final Bundle extras) {
+ if (extras == null) {
+ return;
+ }
if (mExtras == null) {
mExtras = new Bundle();
}
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index c4739ff..1577a0f 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -214,18 +214,27 @@
conference.addConnection(c);
}
}
-
if (conference.getConnections().size() == 0) {
// A conference was created, but none of its connections are ones that have been
// created by, and therefore being tracked by, this remote connection service. It
// is of no interest to us.
+ Log.d(this, "addConferenceCall - skipping");
return;
}
conference.setState(parcel.getState());
conference.setConnectionCapabilities(parcel.getConnectionCapabilities());
conference.setConnectionProperties(parcel.getConnectionProperties());
+ conference.putExtras(parcel.getExtras());
mConferenceById.put(callId, conference);
+
+ // Stash the original connection ID as it exists in the source ConnectionService.
+ // Telecom will use this to avoid adding duplicates later.
+ // See comments on Connection.EXTRA_ORIGINAL_CONNECTION_ID for more information.
+ Bundle newExtras = new Bundle();
+ newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
+ conference.putExtras(newExtras);
+
conference.registerCallback(new RemoteConference.Callback() {
@Override
public void onDestroyed(RemoteConference c) {
@@ -331,12 +340,18 @@
}
@Override
- public void addExistingConnection(String callId, ParcelableConnection connection) {
- // TODO: add contents of this method
- RemoteConnection remoteConnction = new RemoteConnection(callId,
+ public void addExistingConnection(final String callId, ParcelableConnection connection) {
+ RemoteConnection remoteConnection = new RemoteConnection(callId,
mOutgoingConnectionServiceRpc, connection);
-
- mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnction);
+ mConnectionById.put(callId, remoteConnection);
+ remoteConnection.registerCallback(new RemoteConnection.Callback() {
+ @Override
+ public void onDestroyed(RemoteConnection connection) {
+ mConnectionById.remove(callId);
+ maybeDisconnectAdapter();
+ }
+ });
+ mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnection);
}
@Override
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 5b63199..fe9d41a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3161,7 +3161,10 @@
* methods may return true.
*
* <p>This method returns valid data for registered cells on devices with
- * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY}.
+ * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY}. In cases where only
+ * partial information is available for a particular CellInfo entry, unavailable fields
+ * will be reported as Integer.MAX_VALUE. All reported cells will include at least a
+ * valid set of technology-specific identification info and a power level measurement.
*
*<p>
* This method is preferred over using {@link
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index 8424344..f737b24 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -25,6 +25,7 @@
import junit.framework.TestCase;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.view.Display.DEFAULT_DISPLAY;
/**
* TODO: Remove this. This is only a placeholder, need to implement this.
@@ -113,7 +114,8 @@
}
try {
- mWm.updateOrientationFromAppTokens(new Configuration(), null);
+ mWm.updateOrientationFromAppTokens(new Configuration(),
+ null /* freezeThisOneIfNeeded */, DEFAULT_DISPLAY);
fail("IWindowManager.updateOrientationFromAppTokens did not throw SecurityException as"
+ " expected");
} catch (SecurityException e) {
diff --git a/tools/aapt2/.clang-format b/tools/aapt2/.clang-format
new file mode 100644
index 0000000..71c5ef2
--- /dev/null
+++ b/tools/aapt2/.clang-format
@@ -0,0 +1,3 @@
+BasedOnStyle: Google
+ColumnLimit: 100
+
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 6bfedf3..284c787 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -83,7 +83,7 @@
sources += Format.proto
-sourcesJni :=
+sourcesJni := jni/aapt2_jni.cpp
testSources := \
compile/IdAssigner_test.cpp \
@@ -194,15 +194,18 @@
include $(CLEAR_VARS)
LOCAL_MODULE := libaapt2_jni
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
-LOCAL_MODULE_HOST_OS := darwin linux windows
+LOCAL_MODULE_HOST_OS := darwin linux
LOCAL_CFLAGS := $(cFlags)
LOCAL_CFLAGS_darwin := $(cFlags_darwin)
LOCAL_CFLAGS_windows := $(cFlags_windows)
LOCAL_CPPFLAGS := $(cppFlags)
LOCAL_C_INCLUDES := $(protoIncludes)
-LOCAL_SRC_FILES := $(sourcesJni)
-LOCAL_STATIC_LIBRARIES := libaapt2 $(hostStaticLibs)
+LOCAL_SRC_FILES := $(toolSources) $(sourcesJni)
+LOCAL_STATIC_LIBRARIES := libaapt2 libnativehelper $(hostStaticLibs)
LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows)
+LOCAL_LDLIBS := $(hostLdLibs)
+LOCAL_LDLIBS_darwin := $(hostLdLibs_darwin)
+LOCAL_LDLIBS_linux := $(hostLdLibs_linux)
include $(BUILD_HOST_SHARED_LIBRARY)
diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h
index a9794a4..2cbe117 100644
--- a/tools/aapt2/AppInfo.h
+++ b/tools/aapt2/AppInfo.h
@@ -28,27 +28,27 @@
* will come from the app's AndroidManifest.
*/
struct AppInfo {
- /**
- * App's package name.
- */
- std::string package;
+ /**
+ * App's package name.
+ */
+ std::string package;
- /**
- * The App's minimum SDK version.
- */
- Maybe<std::string> minSdkVersion;
+ /**
+ * The App's minimum SDK version.
+ */
+ Maybe<std::string> minSdkVersion;
- /**
- * The Version code of the app.
- */
- Maybe<uint32_t> versionCode;
+ /**
+ * The Version code of the app.
+ */
+ Maybe<uint32_t> versionCode;
- /**
- * The revision code of the app.
- */
- Maybe<uint32_t> revisionCode;
+ /**
+ * The revision code of the app.
+ */
+ Maybe<uint32_t> revisionCode;
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_APP_INFO_H
+#endif // AAPT_APP_INFO_H
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
index 1812d96..6598d63 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -31,854 +31,869 @@
static const char* kWildcardName = "any";
const ConfigDescription& ConfigDescription::defaultConfig() {
- static ConfigDescription config = {};
- return config;
+ static ConfigDescription config = {};
+ return config;
}
static bool parseMcc(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->mcc = 0;
- return true;
- }
- const char* c = name;
- if (tolower(*c) != 'm') return false;
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->mcc = 0;
+ return true;
+ }
+ const char* c = name;
+ if (tolower(*c) != 'm') return false;
+ c++;
+ if (tolower(*c) != 'c') return false;
+ c++;
+ if (tolower(*c) != 'c') return false;
+ c++;
+
+ const char* val = c;
+
+ while (*c >= '0' && *c <= '9') {
c++;
- if (tolower(*c) != 'c') return false;
- c++;
- if (tolower(*c) != 'c') return false;
- c++;
+ }
+ if (*c != 0) return false;
+ if (c - val != 3) return false;
- const char* val = c;
+ int d = atoi(val);
+ if (d != 0) {
+ if (out) out->mcc = d;
+ return true;
+ }
- while (*c >= '0' && *c <= '9') {
- c++;
- }
- if (*c != 0) return false;
- if (c-val != 3) return false;
-
- int d = atoi(val);
- if (d != 0) {
- if (out) out->mcc = d;
- return true;
- }
-
- return false;
+ return false;
}
static bool parseMnc(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->mcc = 0;
- return true;
- }
- const char* c = name;
- if (tolower(*c) != 'm') return false;
- c++;
- if (tolower(*c) != 'n') return false;
- c++;
- if (tolower(*c) != 'c') return false;
- c++;
-
- const char* val = c;
-
- while (*c >= '0' && *c <= '9') {
- c++;
- }
- if (*c != 0) return false;
- if (c-val == 0 || c-val > 3) return false;
-
- if (out) {
- out->mnc = atoi(val);
- if (out->mnc == 0) {
- out->mnc = ACONFIGURATION_MNC_ZERO;
- }
- }
-
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->mcc = 0;
return true;
+ }
+ const char* c = name;
+ if (tolower(*c) != 'm') return false;
+ c++;
+ if (tolower(*c) != 'n') return false;
+ c++;
+ if (tolower(*c) != 'c') return false;
+ c++;
+
+ const char* val = c;
+
+ while (*c >= '0' && *c <= '9') {
+ c++;
+ }
+ if (*c != 0) return false;
+ if (c - val == 0 || c - val > 3) return false;
+
+ if (out) {
+ out->mnc = atoi(val);
+ if (out->mnc == 0) {
+ out->mnc = ACONFIGURATION_MNC_ZERO;
+ }
+ }
+
+ return true;
}
static bool parseLayoutDirection(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
- | ResTable_config::LAYOUTDIR_ANY;
- return true;
- } else if (strcmp(name, "ldltr") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
- | ResTable_config::LAYOUTDIR_LTR;
- return true;
- } else if (strcmp(name, "ldrtl") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
- | ResTable_config::LAYOUTDIR_RTL;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_LAYOUTDIR) |
+ ResTable_config::LAYOUTDIR_ANY;
+ return true;
+ } else if (strcmp(name, "ldltr") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_LAYOUTDIR) |
+ ResTable_config::LAYOUTDIR_LTR;
+ return true;
+ } else if (strcmp(name, "ldrtl") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_LAYOUTDIR) |
+ ResTable_config::LAYOUTDIR_RTL;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseScreenLayoutSize(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
- | ResTable_config::SCREENSIZE_ANY;
- return true;
- } else if (strcmp(name, "small") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
- | ResTable_config::SCREENSIZE_SMALL;
- return true;
- } else if (strcmp(name, "normal") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
- | ResTable_config::SCREENSIZE_NORMAL;
- return true;
- } else if (strcmp(name, "large") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
- | ResTable_config::SCREENSIZE_LARGE;
- return true;
- } else if (strcmp(name, "xlarge") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
- | ResTable_config::SCREENSIZE_XLARGE;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) |
+ ResTable_config::SCREENSIZE_ANY;
+ return true;
+ } else if (strcmp(name, "small") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) |
+ ResTable_config::SCREENSIZE_SMALL;
+ return true;
+ } else if (strcmp(name, "normal") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) |
+ ResTable_config::SCREENSIZE_NORMAL;
+ return true;
+ } else if (strcmp(name, "large") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) |
+ ResTable_config::SCREENSIZE_LARGE;
+ return true;
+ } else if (strcmp(name, "xlarge") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) |
+ ResTable_config::SCREENSIZE_XLARGE;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseScreenLayoutLong(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
- | ResTable_config::SCREENLONG_ANY;
- return true;
- } else if (strcmp(name, "long") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
- | ResTable_config::SCREENLONG_YES;
- return true;
- } else if (strcmp(name, "notlong") == 0) {
- if (out) out->screenLayout =
- (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
- | ResTable_config::SCREENLONG_NO;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENLONG) |
+ ResTable_config::SCREENLONG_ANY;
+ return true;
+ } else if (strcmp(name, "long") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENLONG) |
+ ResTable_config::SCREENLONG_YES;
+ return true;
+ } else if (strcmp(name, "notlong") == 0) {
+ if (out)
+ out->screenLayout =
+ (out->screenLayout & ~ResTable_config::MASK_SCREENLONG) |
+ ResTable_config::SCREENLONG_NO;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseScreenRound(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->screenLayout2 =
- (out->screenLayout2&~ResTable_config::MASK_SCREENROUND)
- | ResTable_config::SCREENROUND_ANY;
- return true;
- } else if (strcmp(name, "round") == 0) {
- if (out) out->screenLayout2 =
- (out->screenLayout2&~ResTable_config::MASK_SCREENROUND)
- | ResTable_config::SCREENROUND_YES;
- return true;
- } else if (strcmp(name, "notround") == 0) {
- if (out) out->screenLayout2 =
- (out->screenLayout2&~ResTable_config::MASK_SCREENROUND)
- | ResTable_config::SCREENROUND_NO;
- return true;
- }
- return false;
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out)
+ out->screenLayout2 =
+ (out->screenLayout2 & ~ResTable_config::MASK_SCREENROUND) |
+ ResTable_config::SCREENROUND_ANY;
+ return true;
+ } else if (strcmp(name, "round") == 0) {
+ if (out)
+ out->screenLayout2 =
+ (out->screenLayout2 & ~ResTable_config::MASK_SCREENROUND) |
+ ResTable_config::SCREENROUND_YES;
+ return true;
+ } else if (strcmp(name, "notround") == 0) {
+ if (out)
+ out->screenLayout2 =
+ (out->screenLayout2 & ~ResTable_config::MASK_SCREENROUND) |
+ ResTable_config::SCREENROUND_NO;
+ return true;
+ }
+ return false;
}
static bool parseOrientation(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->orientation = out->ORIENTATION_ANY;
- return true;
- } else if (strcmp(name, "port") == 0) {
- if (out) out->orientation = out->ORIENTATION_PORT;
- return true;
- } else if (strcmp(name, "land") == 0) {
- if (out) out->orientation = out->ORIENTATION_LAND;
- return true;
- } else if (strcmp(name, "square") == 0) {
- if (out) out->orientation = out->ORIENTATION_SQUARE;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->orientation = out->ORIENTATION_ANY;
+ return true;
+ } else if (strcmp(name, "port") == 0) {
+ if (out) out->orientation = out->ORIENTATION_PORT;
+ return true;
+ } else if (strcmp(name, "land") == 0) {
+ if (out) out->orientation = out->ORIENTATION_LAND;
+ return true;
+ } else if (strcmp(name, "square") == 0) {
+ if (out) out->orientation = out->ORIENTATION_SQUARE;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseUiModeType(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
- | ResTable_config::UI_MODE_TYPE_ANY;
- return true;
- } else if (strcmp(name, "desk") == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
- | ResTable_config::UI_MODE_TYPE_DESK;
- return true;
- } else if (strcmp(name, "car") == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
- | ResTable_config::UI_MODE_TYPE_CAR;
- return true;
- } else if (strcmp(name, "television") == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
- | ResTable_config::UI_MODE_TYPE_TELEVISION;
- return true;
- } else if (strcmp(name, "appliance") == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
- | ResTable_config::UI_MODE_TYPE_APPLIANCE;
- return true;
- } else if (strcmp(name, "watch") == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
- | ResTable_config::UI_MODE_TYPE_WATCH;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+ ResTable_config::UI_MODE_TYPE_ANY;
+ return true;
+ } else if (strcmp(name, "desk") == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+ ResTable_config::UI_MODE_TYPE_DESK;
+ return true;
+ } else if (strcmp(name, "car") == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+ ResTable_config::UI_MODE_TYPE_CAR;
+ return true;
+ } else if (strcmp(name, "television") == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+ ResTable_config::UI_MODE_TYPE_TELEVISION;
+ return true;
+ } else if (strcmp(name, "appliance") == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+ ResTable_config::UI_MODE_TYPE_APPLIANCE;
+ return true;
+ } else if (strcmp(name, "watch") == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+ ResTable_config::UI_MODE_TYPE_WATCH;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseUiModeNight(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
- | ResTable_config::UI_MODE_NIGHT_ANY;
- return true;
- } else if (strcmp(name, "night") == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
- | ResTable_config::UI_MODE_NIGHT_YES;
- return true;
- } else if (strcmp(name, "notnight") == 0) {
- if (out) out->uiMode =
- (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
- | ResTable_config::UI_MODE_NIGHT_NO;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_NIGHT) |
+ ResTable_config::UI_MODE_NIGHT_ANY;
+ return true;
+ } else if (strcmp(name, "night") == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_NIGHT) |
+ ResTable_config::UI_MODE_NIGHT_YES;
+ return true;
+ } else if (strcmp(name, "notnight") == 0) {
+ if (out)
+ out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_NIGHT) |
+ ResTable_config::UI_MODE_NIGHT_NO;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseDensity(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->density = ResTable_config::DENSITY_DEFAULT;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->density = ResTable_config::DENSITY_DEFAULT;
+ return true;
+ }
- if (strcmp(name, "anydpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_ANY;
- return true;
- }
+ if (strcmp(name, "anydpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_ANY;
+ return true;
+ }
- if (strcmp(name, "nodpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_NONE;
- return true;
- }
+ if (strcmp(name, "nodpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_NONE;
+ return true;
+ }
- if (strcmp(name, "ldpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_LOW;
- return true;
- }
+ if (strcmp(name, "ldpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_LOW;
+ return true;
+ }
- if (strcmp(name, "mdpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_MEDIUM;
- return true;
- }
+ if (strcmp(name, "mdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_MEDIUM;
+ return true;
+ }
- if (strcmp(name, "tvdpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_TV;
- return true;
- }
+ if (strcmp(name, "tvdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_TV;
+ return true;
+ }
- if (strcmp(name, "hdpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_HIGH;
- return true;
- }
+ if (strcmp(name, "hdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_HIGH;
+ return true;
+ }
- if (strcmp(name, "xhdpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_XHIGH;
- return true;
- }
+ if (strcmp(name, "xhdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_XHIGH;
+ return true;
+ }
- if (strcmp(name, "xxhdpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_XXHIGH;
- return true;
- }
+ if (strcmp(name, "xxhdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_XXHIGH;
+ return true;
+ }
- if (strcmp(name, "xxxhdpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_XXXHIGH;
- return true;
- }
+ if (strcmp(name, "xxxhdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_XXXHIGH;
+ return true;
+ }
- char* c = (char*)name;
- while (*c >= '0' && *c <= '9') {
- c++;
- }
+ char* c = (char*)name;
+ while (*c >= '0' && *c <= '9') {
+ c++;
+ }
- // check that we have 'dpi' after the last digit.
- if (toupper(c[0]) != 'D' ||
- toupper(c[1]) != 'P' ||
- toupper(c[2]) != 'I' ||
- c[3] != 0) {
- return false;
- }
-
- // temporarily replace the first letter with \0 to
- // use atoi.
- char tmp = c[0];
- c[0] = '\0';
-
- int d = atoi(name);
- c[0] = tmp;
-
- if (d != 0) {
- if (out) out->density = d;
- return true;
- }
-
+ // check that we have 'dpi' after the last digit.
+ if (toupper(c[0]) != 'D' || toupper(c[1]) != 'P' || toupper(c[2]) != 'I' ||
+ c[3] != 0) {
return false;
+ }
+
+ // temporarily replace the first letter with \0 to
+ // use atoi.
+ char tmp = c[0];
+ c[0] = '\0';
+
+ int d = atoi(name);
+ c[0] = tmp;
+
+ if (d != 0) {
+ if (out) out->density = d;
+ return true;
+ }
+
+ return false;
}
static bool parseTouchscreen(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->touchscreen = out->TOUCHSCREEN_ANY;
- return true;
- } else if (strcmp(name, "notouch") == 0) {
- if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH;
- return true;
- } else if (strcmp(name, "stylus") == 0) {
- if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS;
- return true;
- } else if (strcmp(name, "finger") == 0) {
- if (out) out->touchscreen = out->TOUCHSCREEN_FINGER;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_ANY;
+ return true;
+ } else if (strcmp(name, "notouch") == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH;
+ return true;
+ } else if (strcmp(name, "stylus") == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS;
+ return true;
+ } else if (strcmp(name, "finger") == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_FINGER;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseKeysHidden(const char* name, ResTable_config* out) {
- uint8_t mask = 0;
- uint8_t value = 0;
- if (strcmp(name, kWildcardName) == 0) {
- mask = ResTable_config::MASK_KEYSHIDDEN;
- value = ResTable_config::KEYSHIDDEN_ANY;
- } else if (strcmp(name, "keysexposed") == 0) {
- mask = ResTable_config::MASK_KEYSHIDDEN;
- value = ResTable_config::KEYSHIDDEN_NO;
- } else if (strcmp(name, "keyshidden") == 0) {
- mask = ResTable_config::MASK_KEYSHIDDEN;
- value = ResTable_config::KEYSHIDDEN_YES;
- } else if (strcmp(name, "keyssoft") == 0) {
- mask = ResTable_config::MASK_KEYSHIDDEN;
- value = ResTable_config::KEYSHIDDEN_SOFT;
- }
+ uint8_t mask = 0;
+ uint8_t value = 0;
+ if (strcmp(name, kWildcardName) == 0) {
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_ANY;
+ } else if (strcmp(name, "keysexposed") == 0) {
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_NO;
+ } else if (strcmp(name, "keyshidden") == 0) {
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_YES;
+ } else if (strcmp(name, "keyssoft") == 0) {
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_SOFT;
+ }
- if (mask != 0) {
- if (out) out->inputFlags = (out->inputFlags&~mask) | value;
- return true;
- }
+ if (mask != 0) {
+ if (out) out->inputFlags = (out->inputFlags & ~mask) | value;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseKeyboard(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->keyboard = out->KEYBOARD_ANY;
- return true;
- } else if (strcmp(name, "nokeys") == 0) {
- if (out) out->keyboard = out->KEYBOARD_NOKEYS;
- return true;
- } else if (strcmp(name, "qwerty") == 0) {
- if (out) out->keyboard = out->KEYBOARD_QWERTY;
- return true;
- } else if (strcmp(name, "12key") == 0) {
- if (out) out->keyboard = out->KEYBOARD_12KEY;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->keyboard = out->KEYBOARD_ANY;
+ return true;
+ } else if (strcmp(name, "nokeys") == 0) {
+ if (out) out->keyboard = out->KEYBOARD_NOKEYS;
+ return true;
+ } else if (strcmp(name, "qwerty") == 0) {
+ if (out) out->keyboard = out->KEYBOARD_QWERTY;
+ return true;
+ } else if (strcmp(name, "12key") == 0) {
+ if (out) out->keyboard = out->KEYBOARD_12KEY;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseNavHidden(const char* name, ResTable_config* out) {
- uint8_t mask = 0;
- uint8_t value = 0;
- if (strcmp(name, kWildcardName) == 0) {
- mask = ResTable_config::MASK_NAVHIDDEN;
- value = ResTable_config::NAVHIDDEN_ANY;
- } else if (strcmp(name, "navexposed") == 0) {
- mask = ResTable_config::MASK_NAVHIDDEN;
- value = ResTable_config::NAVHIDDEN_NO;
- } else if (strcmp(name, "navhidden") == 0) {
- mask = ResTable_config::MASK_NAVHIDDEN;
- value = ResTable_config::NAVHIDDEN_YES;
- }
+ uint8_t mask = 0;
+ uint8_t value = 0;
+ if (strcmp(name, kWildcardName) == 0) {
+ mask = ResTable_config::MASK_NAVHIDDEN;
+ value = ResTable_config::NAVHIDDEN_ANY;
+ } else if (strcmp(name, "navexposed") == 0) {
+ mask = ResTable_config::MASK_NAVHIDDEN;
+ value = ResTable_config::NAVHIDDEN_NO;
+ } else if (strcmp(name, "navhidden") == 0) {
+ mask = ResTable_config::MASK_NAVHIDDEN;
+ value = ResTable_config::NAVHIDDEN_YES;
+ }
- if (mask != 0) {
- if (out) out->inputFlags = (out->inputFlags&~mask) | value;
- return true;
- }
+ if (mask != 0) {
+ if (out) out->inputFlags = (out->inputFlags & ~mask) | value;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseNavigation(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) out->navigation = out->NAVIGATION_ANY;
- return true;
- } else if (strcmp(name, "nonav") == 0) {
- if (out) out->navigation = out->NAVIGATION_NONAV;
- return true;
- } else if (strcmp(name, "dpad") == 0) {
- if (out) out->navigation = out->NAVIGATION_DPAD;
- return true;
- } else if (strcmp(name, "trackball") == 0) {
- if (out) out->navigation = out->NAVIGATION_TRACKBALL;
- return true;
- } else if (strcmp(name, "wheel") == 0) {
- if (out) out->navigation = out->NAVIGATION_WHEEL;
- return true;
- }
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->navigation = out->NAVIGATION_ANY;
+ return true;
+ } else if (strcmp(name, "nonav") == 0) {
+ if (out) out->navigation = out->NAVIGATION_NONAV;
+ return true;
+ } else if (strcmp(name, "dpad") == 0) {
+ if (out) out->navigation = out->NAVIGATION_DPAD;
+ return true;
+ } else if (strcmp(name, "trackball") == 0) {
+ if (out) out->navigation = out->NAVIGATION_TRACKBALL;
+ return true;
+ } else if (strcmp(name, "wheel") == 0) {
+ if (out) out->navigation = out->NAVIGATION_WHEEL;
+ return true;
+ }
- return false;
+ return false;
}
static bool parseScreenSize(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) {
- out->screenWidth = out->SCREENWIDTH_ANY;
- out->screenHeight = out->SCREENHEIGHT_ANY;
- }
- return true;
- }
-
- const char* x = name;
- while (*x >= '0' && *x <= '9') x++;
- if (x == name || *x != 'x') return false;
- std::string xName(name, x-name);
- x++;
-
- const char* y = x;
- while (*y >= '0' && *y <= '9') y++;
- if (y == name || *y != 0) return false;
- std::string yName(x, y-x);
-
- uint16_t w = (uint16_t)atoi(xName.c_str());
- uint16_t h = (uint16_t)atoi(yName.c_str());
- if (w < h) {
- return false;
- }
-
+ if (strcmp(name, kWildcardName) == 0) {
if (out) {
- out->screenWidth = w;
- out->screenHeight = h;
+ out->screenWidth = out->SCREENWIDTH_ANY;
+ out->screenHeight = out->SCREENHEIGHT_ANY;
}
-
return true;
+ }
+
+ const char* x = name;
+ while (*x >= '0' && *x <= '9') x++;
+ if (x == name || *x != 'x') return false;
+ std::string xName(name, x - name);
+ x++;
+
+ const char* y = x;
+ while (*y >= '0' && *y <= '9') y++;
+ if (y == name || *y != 0) return false;
+ std::string yName(x, y - x);
+
+ uint16_t w = (uint16_t)atoi(xName.c_str());
+ uint16_t h = (uint16_t)atoi(yName.c_str());
+ if (w < h) {
+ return false;
+ }
+
+ if (out) {
+ out->screenWidth = w;
+ out->screenHeight = h;
+ }
+
+ return true;
}
static bool parseSmallestScreenWidthDp(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) {
- out->smallestScreenWidthDp = out->SCREENWIDTH_ANY;
- }
- return true;
- }
-
- if (*name != 's') return false;
- name++;
- if (*name != 'w') return false;
- name++;
- const char* x = name;
- while (*x >= '0' && *x <= '9') x++;
- if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
- std::string xName(name, x-name);
-
+ if (strcmp(name, kWildcardName) == 0) {
if (out) {
- out->smallestScreenWidthDp = (uint16_t)atoi(xName.c_str());
+ out->smallestScreenWidthDp = out->SCREENWIDTH_ANY;
}
-
return true;
+ }
+
+ if (*name != 's') return false;
+ name++;
+ if (*name != 'w') return false;
+ name++;
+ const char* x = name;
+ while (*x >= '0' && *x <= '9') x++;
+ if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+ std::string xName(name, x - name);
+
+ if (out) {
+ out->smallestScreenWidthDp = (uint16_t)atoi(xName.c_str());
+ }
+
+ return true;
}
static bool parseScreenWidthDp(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) {
- out->screenWidthDp = out->SCREENWIDTH_ANY;
- }
- return true;
- }
-
- if (*name != 'w') return false;
- name++;
- const char* x = name;
- while (*x >= '0' && *x <= '9') x++;
- if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
- std::string xName(name, x-name);
-
+ if (strcmp(name, kWildcardName) == 0) {
if (out) {
- out->screenWidthDp = (uint16_t)atoi(xName.c_str());
+ out->screenWidthDp = out->SCREENWIDTH_ANY;
}
-
return true;
+ }
+
+ if (*name != 'w') return false;
+ name++;
+ const char* x = name;
+ while (*x >= '0' && *x <= '9') x++;
+ if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+ std::string xName(name, x - name);
+
+ if (out) {
+ out->screenWidthDp = (uint16_t)atoi(xName.c_str());
+ }
+
+ return true;
}
static bool parseScreenHeightDp(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) {
- out->screenHeightDp = out->SCREENWIDTH_ANY;
- }
- return true;
- }
-
- if (*name != 'h') return false;
- name++;
- const char* x = name;
- while (*x >= '0' && *x <= '9') x++;
- if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
- std::string xName(name, x-name);
-
+ if (strcmp(name, kWildcardName) == 0) {
if (out) {
- out->screenHeightDp = (uint16_t)atoi(xName.c_str());
+ out->screenHeightDp = out->SCREENWIDTH_ANY;
}
-
return true;
+ }
+
+ if (*name != 'h') return false;
+ name++;
+ const char* x = name;
+ while (*x >= '0' && *x <= '9') x++;
+ if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+ std::string xName(name, x - name);
+
+ if (out) {
+ out->screenHeightDp = (uint16_t)atoi(xName.c_str());
+ }
+
+ return true;
}
static bool parseVersion(const char* name, ResTable_config* out) {
- if (strcmp(name, kWildcardName) == 0) {
- if (out) {
- out->sdkVersion = out->SDKVERSION_ANY;
- out->minorVersion = out->MINORVERSION_ANY;
- }
- return true;
- }
-
- if (*name != 'v') {
- return false;
- }
-
- name++;
- const char* s = name;
- while (*s >= '0' && *s <= '9') s++;
- if (s == name || *s != 0) return false;
- std::string sdkName(name, s-name);
-
+ if (strcmp(name, kWildcardName) == 0) {
if (out) {
- out->sdkVersion = (uint16_t)atoi(sdkName.c_str());
- out->minorVersion = 0;
+ out->sdkVersion = out->SDKVERSION_ANY;
+ out->minorVersion = out->MINORVERSION_ANY;
}
-
return true;
+ }
+
+ if (*name != 'v') {
+ return false;
+ }
+
+ name++;
+ const char* s = name;
+ while (*s >= '0' && *s <= '9') s++;
+ if (s == name || *s != 0) return false;
+ std::string sdkName(name, s - name);
+
+ if (out) {
+ out->sdkVersion = (uint16_t)atoi(sdkName.c_str());
+ out->minorVersion = 0;
+ }
+
+ return true;
}
bool ConfigDescription::parse(const StringPiece& str, ConfigDescription* out) {
- std::vector<std::string> parts = util::splitAndLowercase(str, '-');
+ std::vector<std::string> parts = util::splitAndLowercase(str, '-');
- ConfigDescription config;
- ssize_t partsConsumed = 0;
- LocaleValue locale;
+ ConfigDescription config;
+ ssize_t partsConsumed = 0;
+ LocaleValue locale;
- const auto partsEnd = parts.end();
- auto partIter = parts.begin();
+ const auto partsEnd = parts.end();
+ auto partIter = parts.begin();
- if (str.size() == 0) {
- goto success;
+ if (str.size() == 0) {
+ goto success;
+ }
+
+ if (parseMcc(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
}
+ }
- if (parseMcc(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseMnc(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
}
+ }
- if (parseMnc(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- // Locale spans a few '-' separators, so we let it
- // control the index.
- partsConsumed = locale.initFromParts(partIter, partsEnd);
- if (partsConsumed < 0) {
- return false;
- } else {
- locale.writeTo(&config);
- partIter += partsConsumed;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseLayoutDirection(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseSmallestScreenWidthDp(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseScreenWidthDp(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseScreenHeightDp(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseScreenLayoutSize(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseScreenLayoutLong(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseScreenRound(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseOrientation(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseUiModeType(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseUiModeNight(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseDensity(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseTouchscreen(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseKeysHidden(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseKeyboard(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseNavHidden(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseNavigation(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseScreenSize(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- if (parseVersion(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
- }
-
- // Unrecognized.
+ // Locale spans a few '-' separators, so we let it
+ // control the index.
+ partsConsumed = locale.initFromParts(partIter, partsEnd);
+ if (partsConsumed < 0) {
return false;
+ } else {
+ locale.writeTo(&config);
+ partIter += partsConsumed;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseLayoutDirection(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseSmallestScreenWidthDp(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseScreenWidthDp(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseScreenHeightDp(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseScreenLayoutSize(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseScreenLayoutLong(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseScreenRound(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseOrientation(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseUiModeType(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseUiModeNight(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseDensity(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseTouchscreen(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseKeysHidden(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseKeyboard(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseNavHidden(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseNavigation(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseScreenSize(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ if (parseVersion(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
+ // Unrecognized.
+ return false;
success:
- if (out != NULL) {
- applyVersionForCompatibility(&config);
- *out = config;
- }
- return true;
+ if (out != NULL) {
+ applyVersionForCompatibility(&config);
+ *out = config;
+ }
+ return true;
}
-void ConfigDescription::applyVersionForCompatibility(ConfigDescription* config) {
- uint16_t minSdk = 0;
- if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
- minSdk = SDK_MARSHMALLOW;
- } else if (config->density == ResTable_config::DENSITY_ANY) {
- minSdk = SDK_LOLLIPOP;
- } else if (config->smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY
- || config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY
- || config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
- minSdk = SDK_HONEYCOMB_MR2;
- } else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
- != ResTable_config::UI_MODE_TYPE_ANY
- || (config->uiMode & ResTable_config::MASK_UI_MODE_NIGHT)
- != ResTable_config::UI_MODE_NIGHT_ANY) {
- minSdk = SDK_FROYO;
- } else if ((config->screenLayout & ResTable_config::MASK_SCREENSIZE)
- != ResTable_config::SCREENSIZE_ANY
- || (config->screenLayout & ResTable_config::MASK_SCREENLONG)
- != ResTable_config::SCREENLONG_ANY
- || config->density != ResTable_config::DENSITY_DEFAULT) {
- minSdk = SDK_DONUT;
- }
+void ConfigDescription::applyVersionForCompatibility(
+ ConfigDescription* config) {
+ uint16_t minSdk = 0;
+ if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
+ minSdk = SDK_MARSHMALLOW;
+ } else if (config->density == ResTable_config::DENSITY_ANY) {
+ minSdk = SDK_LOLLIPOP;
+ } else if (config->smallestScreenWidthDp !=
+ ResTable_config::SCREENWIDTH_ANY ||
+ config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY ||
+ config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
+ minSdk = SDK_HONEYCOMB_MR2;
+ } else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE) !=
+ ResTable_config::UI_MODE_TYPE_ANY ||
+ (config->uiMode & ResTable_config::MASK_UI_MODE_NIGHT) !=
+ ResTable_config::UI_MODE_NIGHT_ANY) {
+ minSdk = SDK_FROYO;
+ } else if ((config->screenLayout & ResTable_config::MASK_SCREENSIZE) !=
+ ResTable_config::SCREENSIZE_ANY ||
+ (config->screenLayout & ResTable_config::MASK_SCREENLONG) !=
+ ResTable_config::SCREENLONG_ANY ||
+ config->density != ResTable_config::DENSITY_DEFAULT) {
+ minSdk = SDK_DONUT;
+ }
- if (minSdk > config->sdkVersion) {
- config->sdkVersion = minSdk;
- }
+ if (minSdk > config->sdkVersion) {
+ config->sdkVersion = minSdk;
+ }
}
ConfigDescription ConfigDescription::copyWithoutSdkVersion() const {
- ConfigDescription copy = *this;
- copy.sdkVersion = 0;
- return copy;
+ ConfigDescription copy = *this;
+ copy.sdkVersion = 0;
+ return copy;
}
bool ConfigDescription::dominates(const ConfigDescription& o) const {
- if (*this == defaultConfig() || *this == o) {
- return true;
- }
- return matchWithDensity(o)
- && !o.matchWithDensity(*this)
- && !isMoreSpecificThan(o)
- && !o.hasHigherPrecedenceThan(*this);
+ if (*this == defaultConfig() || *this == o) {
+ return true;
+ }
+ return matchWithDensity(o) && !o.matchWithDensity(*this) &&
+ !isMoreSpecificThan(o) && !o.hasHigherPrecedenceThan(*this);
}
-bool ConfigDescription::hasHigherPrecedenceThan(const ConfigDescription& o) const {
- // The order of the following tests defines the importance of one
- // configuration parameter over another. Those tests first are more
- // important, trumping any values in those following them.
- // The ordering should be the same as ResTable_config#isBetterThan.
- if (mcc || o.mcc) return (!o.mcc);
- if (mnc || o.mnc) return (!o.mnc);
- if (language[0] || o.language[0]) return (!o.language[0]);
- if (country[0] || o.country[0]) return (!o.country[0]);
- // Script and variant require either a language or country, both of which
- // have higher precedence.
- if ((screenLayout | o.screenLayout) & MASK_LAYOUTDIR) {
- return !(o.screenLayout & MASK_LAYOUTDIR);
- }
- if (smallestScreenWidthDp || o.smallestScreenWidthDp) return (!o.smallestScreenWidthDp);
- if (screenWidthDp || o.screenWidthDp) return (!o.screenWidthDp);
- if (screenHeightDp || o.screenHeightDp) return (!o.screenHeightDp);
- if ((screenLayout | o.screenLayout) & MASK_SCREENSIZE) {
- return !(o.screenLayout & MASK_SCREENSIZE);
- }
- if ((screenLayout | o.screenLayout) & MASK_SCREENLONG) {
- return !(o.screenLayout & MASK_SCREENLONG);
- }
- if ((screenLayout2 | o.screenLayout2) & MASK_SCREENROUND) {
- return !(o.screenLayout2 & MASK_SCREENROUND);
- }
- if (orientation || o.orientation) return (!o.orientation);
- if ((uiMode | o.uiMode) & MASK_UI_MODE_TYPE) {
- return !(o.uiMode & MASK_UI_MODE_TYPE);
- }
- if ((uiMode | o.uiMode) & MASK_UI_MODE_NIGHT) {
- return !(o.uiMode & MASK_UI_MODE_NIGHT);
- }
- if (density || o.density) return (!o.density);
- if (touchscreen || o.touchscreen) return (!o.touchscreen);
- if ((inputFlags | o.inputFlags) & MASK_KEYSHIDDEN) {
- return !(o.inputFlags & MASK_KEYSHIDDEN);
- }
- if ((inputFlags | o.inputFlags) & MASK_NAVHIDDEN) {
- return !(o.inputFlags & MASK_NAVHIDDEN);
- }
- if (keyboard || o.keyboard) return (!o.keyboard);
- if (navigation || o.navigation) return (!o.navigation);
- if (screenWidth || o.screenWidth) return (!o.screenWidth);
- if (screenHeight || o.screenHeight) return (!o.screenHeight);
- if (sdkVersion || o.sdkVersion) return (!o.sdkVersion);
- if (minorVersion || o.minorVersion) return (!o.minorVersion);
- // Both configurations have nothing defined except some possible future
- // value. Returning the comparison of the two configurations is a
- // "best effort" at this point to protect against incorrect dominations.
- return *this != o;
+bool ConfigDescription::hasHigherPrecedenceThan(
+ const ConfigDescription& o) const {
+ // The order of the following tests defines the importance of one
+ // configuration parameter over another. Those tests first are more
+ // important, trumping any values in those following them.
+ // The ordering should be the same as ResTable_config#isBetterThan.
+ if (mcc || o.mcc) return (!o.mcc);
+ if (mnc || o.mnc) return (!o.mnc);
+ if (language[0] || o.language[0]) return (!o.language[0]);
+ if (country[0] || o.country[0]) return (!o.country[0]);
+ // Script and variant require either a language or country, both of which
+ // have higher precedence.
+ if ((screenLayout | o.screenLayout) & MASK_LAYOUTDIR) {
+ return !(o.screenLayout & MASK_LAYOUTDIR);
+ }
+ if (smallestScreenWidthDp || o.smallestScreenWidthDp)
+ return (!o.smallestScreenWidthDp);
+ if (screenWidthDp || o.screenWidthDp) return (!o.screenWidthDp);
+ if (screenHeightDp || o.screenHeightDp) return (!o.screenHeightDp);
+ if ((screenLayout | o.screenLayout) & MASK_SCREENSIZE) {
+ return !(o.screenLayout & MASK_SCREENSIZE);
+ }
+ if ((screenLayout | o.screenLayout) & MASK_SCREENLONG) {
+ return !(o.screenLayout & MASK_SCREENLONG);
+ }
+ if ((screenLayout2 | o.screenLayout2) & MASK_SCREENROUND) {
+ return !(o.screenLayout2 & MASK_SCREENROUND);
+ }
+ if (orientation || o.orientation) return (!o.orientation);
+ if ((uiMode | o.uiMode) & MASK_UI_MODE_TYPE) {
+ return !(o.uiMode & MASK_UI_MODE_TYPE);
+ }
+ if ((uiMode | o.uiMode) & MASK_UI_MODE_NIGHT) {
+ return !(o.uiMode & MASK_UI_MODE_NIGHT);
+ }
+ if (density || o.density) return (!o.density);
+ if (touchscreen || o.touchscreen) return (!o.touchscreen);
+ if ((inputFlags | o.inputFlags) & MASK_KEYSHIDDEN) {
+ return !(o.inputFlags & MASK_KEYSHIDDEN);
+ }
+ if ((inputFlags | o.inputFlags) & MASK_NAVHIDDEN) {
+ return !(o.inputFlags & MASK_NAVHIDDEN);
+ }
+ if (keyboard || o.keyboard) return (!o.keyboard);
+ if (navigation || o.navigation) return (!o.navigation);
+ if (screenWidth || o.screenWidth) return (!o.screenWidth);
+ if (screenHeight || o.screenHeight) return (!o.screenHeight);
+ if (sdkVersion || o.sdkVersion) return (!o.sdkVersion);
+ if (minorVersion || o.minorVersion) return (!o.minorVersion);
+ // Both configurations have nothing defined except some possible future
+ // value. Returning the comparison of the two configurations is a
+ // "best effort" at this point to protect against incorrect dominations.
+ return *this != o;
}
bool ConfigDescription::conflictsWith(const ConfigDescription& o) const {
- // This method should be updated as new configuration parameters are
- // introduced (e.g. screenConfig2).
- auto pred = [](const uint32_t a, const uint32_t b) -> bool {
- return a == 0 || b == 0 || a == b;
- };
- // The values here can be found in ResTable_config#match. Density and range
- // values can't lead to conflicts, and are ignored.
- return !pred(mcc, o.mcc)
- || !pred(mnc, o.mnc)
- || !pred(locale, o.locale)
- || !pred(screenLayout & MASK_LAYOUTDIR, o.screenLayout & MASK_LAYOUTDIR)
- || !pred(screenLayout & MASK_SCREENLONG, o.screenLayout & MASK_SCREENLONG)
- || !pred(screenLayout & MASK_UI_MODE_TYPE, o.screenLayout & MASK_UI_MODE_TYPE)
- || !pred(uiMode & MASK_UI_MODE_TYPE, o.uiMode & MASK_UI_MODE_TYPE)
- || !pred(uiMode & MASK_UI_MODE_NIGHT, o.uiMode & MASK_UI_MODE_NIGHT)
- || !pred(screenLayout2 & MASK_SCREENROUND, o.screenLayout2 & MASK_SCREENROUND)
- || !pred(orientation, o.orientation)
- || !pred(touchscreen, o.touchscreen)
- || !pred(inputFlags & MASK_KEYSHIDDEN, o.inputFlags & MASK_KEYSHIDDEN)
- || !pred(inputFlags & MASK_NAVHIDDEN, o.inputFlags & MASK_NAVHIDDEN)
- || !pred(keyboard, o.keyboard)
- || !pred(navigation, o.navigation);
+ // This method should be updated as new configuration parameters are
+ // introduced (e.g. screenConfig2).
+ auto pred = [](const uint32_t a, const uint32_t b) -> bool {
+ return a == 0 || b == 0 || a == b;
+ };
+ // The values here can be found in ResTable_config#match. Density and range
+ // values can't lead to conflicts, and are ignored.
+ return !pred(mcc, o.mcc) || !pred(mnc, o.mnc) || !pred(locale, o.locale) ||
+ !pred(screenLayout & MASK_LAYOUTDIR,
+ o.screenLayout & MASK_LAYOUTDIR) ||
+ !pred(screenLayout & MASK_SCREENLONG,
+ o.screenLayout & MASK_SCREENLONG) ||
+ !pred(screenLayout & MASK_UI_MODE_TYPE,
+ o.screenLayout & MASK_UI_MODE_TYPE) ||
+ !pred(uiMode & MASK_UI_MODE_TYPE, o.uiMode & MASK_UI_MODE_TYPE) ||
+ !pred(uiMode & MASK_UI_MODE_NIGHT, o.uiMode & MASK_UI_MODE_NIGHT) ||
+ !pred(screenLayout2 & MASK_SCREENROUND,
+ o.screenLayout2 & MASK_SCREENROUND) ||
+ !pred(orientation, o.orientation) ||
+ !pred(touchscreen, o.touchscreen) ||
+ !pred(inputFlags & MASK_KEYSHIDDEN, o.inputFlags & MASK_KEYSHIDDEN) ||
+ !pred(inputFlags & MASK_NAVHIDDEN, o.inputFlags & MASK_NAVHIDDEN) ||
+ !pred(keyboard, o.keyboard) || !pred(navigation, o.navigation);
}
bool ConfigDescription::isCompatibleWith(const ConfigDescription& o) const {
- return !conflictsWith(o) && !dominates(o) && !o.dominates(*this);
+ return !conflictsWith(o) && !dominates(o) && !o.dominates(*this);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h
index d801621..bb54886 100644
--- a/tools/aapt2/ConfigDescription.h
+++ b/tools/aapt2/ConfigDescription.h
@@ -29,148 +29,152 @@
* initialization and comparison methods.
*/
struct ConfigDescription : public android::ResTable_config {
- /**
- * Returns an immutable default config.
- */
- static const ConfigDescription& defaultConfig();
+ /**
+ * Returns an immutable default config.
+ */
+ static const ConfigDescription& defaultConfig();
- /*
- * Parse a string of the form 'fr-sw600dp-land' and fill in the
- * given ResTable_config with resulting configuration parameters.
- *
- * The resulting configuration has the appropriate sdkVersion defined
- * for backwards compatibility.
- */
- static bool parse(const StringPiece& str, ConfigDescription* out = nullptr);
+ /*
+ * Parse a string of the form 'fr-sw600dp-land' and fill in the
+ * given ResTable_config with resulting configuration parameters.
+ *
+ * The resulting configuration has the appropriate sdkVersion defined
+ * for backwards compatibility.
+ */
+ static bool parse(const StringPiece& str, ConfigDescription* out = nullptr);
- /**
- * If the configuration uses an axis that was added after
- * the original Android release, make sure the SDK version
- * is set accordingly.
- */
- static void applyVersionForCompatibility(ConfigDescription* config);
+ /**
+ * If the configuration uses an axis that was added after
+ * the original Android release, make sure the SDK version
+ * is set accordingly.
+ */
+ static void applyVersionForCompatibility(ConfigDescription* config);
- ConfigDescription();
- ConfigDescription(const android::ResTable_config& o); // NOLINT(implicit)
- ConfigDescription(const ConfigDescription& o);
- ConfigDescription(ConfigDescription&& o);
+ ConfigDescription();
+ ConfigDescription(const android::ResTable_config& o); // NOLINT(implicit)
+ ConfigDescription(const ConfigDescription& o);
+ ConfigDescription(ConfigDescription&& o);
- ConfigDescription& operator=(const android::ResTable_config& o);
- ConfigDescription& operator=(const ConfigDescription& o);
- ConfigDescription& operator=(ConfigDescription&& o);
+ ConfigDescription& operator=(const android::ResTable_config& o);
+ ConfigDescription& operator=(const ConfigDescription& o);
+ ConfigDescription& operator=(ConfigDescription&& o);
- ConfigDescription copyWithoutSdkVersion() const;
+ ConfigDescription copyWithoutSdkVersion() const;
- /**
- * A configuration X dominates another configuration Y, if X has at least the
- * precedence of Y and X is strictly more general than Y: for any type defined
- * by X, the same type is defined by Y with a value equal to or, in the case
- * of ranges, more specific than that of X.
- *
- * For example, the configuration 'en-w800dp' dominates 'en-rGB-w1024dp'. It
- * does not dominate 'fr', 'en-w720dp', or 'mcc001-en-w800dp'.
- */
- bool dominates(const ConfigDescription& o) const;
+ /**
+ * A configuration X dominates another configuration Y, if X has at least the
+ * precedence of Y and X is strictly more general than Y: for any type defined
+ * by X, the same type is defined by Y with a value equal to or, in the case
+ * of ranges, more specific than that of X.
+ *
+ * For example, the configuration 'en-w800dp' dominates 'en-rGB-w1024dp'. It
+ * does not dominate 'fr', 'en-w720dp', or 'mcc001-en-w800dp'.
+ */
+ bool dominates(const ConfigDescription& o) const;
- /**
- * Returns true if this configuration defines a more important configuration
- * parameter than o. For example, "en" has higher precedence than "v23",
- * whereas "en" has the same precedence as "en-v23".
- */
- bool hasHigherPrecedenceThan(const ConfigDescription& o) const;
+ /**
+ * Returns true if this configuration defines a more important configuration
+ * parameter than o. For example, "en" has higher precedence than "v23",
+ * whereas "en" has the same precedence as "en-v23".
+ */
+ bool hasHigherPrecedenceThan(const ConfigDescription& o) const;
- /**
- * A configuration conflicts with another configuration if both
- * configurations define an incompatible configuration parameter. An
- * incompatible configuration parameter is a non-range, non-density parameter
- * that is defined in both configurations as a different, non-default value.
- */
- bool conflictsWith(const ConfigDescription& o) const;
+ /**
+ * A configuration conflicts with another configuration if both
+ * configurations define an incompatible configuration parameter. An
+ * incompatible configuration parameter is a non-range, non-density parameter
+ * that is defined in both configurations as a different, non-default value.
+ */
+ bool conflictsWith(const ConfigDescription& o) const;
- /**
- * A configuration is compatible with another configuration if both
- * configurations can match a common concrete device configuration and are
- * unrelated by domination. For example, land-v11 conflicts with port-v21
- * but is compatible with v21 (both land-v11 and v21 would match en-land-v23).
- */
- bool isCompatibleWith(const ConfigDescription& o) const;
+ /**
+ * A configuration is compatible with another configuration if both
+ * configurations can match a common concrete device configuration and are
+ * unrelated by domination. For example, land-v11 conflicts with port-v21
+ * but is compatible with v21 (both land-v11 and v21 would match en-land-v23).
+ */
+ bool isCompatibleWith(const ConfigDescription& o) const;
- bool matchWithDensity(const ConfigDescription& o) const;
+ bool matchWithDensity(const ConfigDescription& o) const;
- bool operator<(const ConfigDescription& o) const;
- bool operator<=(const ConfigDescription& o) const;
- bool operator==(const ConfigDescription& o) const;
- bool operator!=(const ConfigDescription& o) const;
- bool operator>=(const ConfigDescription& o) const;
- bool operator>(const ConfigDescription& o) const;
+ bool operator<(const ConfigDescription& o) const;
+ bool operator<=(const ConfigDescription& o) const;
+ bool operator==(const ConfigDescription& o) const;
+ bool operator!=(const ConfigDescription& o) const;
+ bool operator>=(const ConfigDescription& o) const;
+ bool operator>(const ConfigDescription& o) const;
};
inline ConfigDescription::ConfigDescription() {
- memset(this, 0, sizeof(*this));
- size = sizeof(android::ResTable_config);
+ memset(this, 0, sizeof(*this));
+ size = sizeof(android::ResTable_config);
}
inline ConfigDescription::ConfigDescription(const android::ResTable_config& o) {
- *static_cast<android::ResTable_config*>(this) = o;
- size = sizeof(android::ResTable_config);
+ *static_cast<android::ResTable_config*>(this) = o;
+ size = sizeof(android::ResTable_config);
}
inline ConfigDescription::ConfigDescription(const ConfigDescription& o) {
- *static_cast<android::ResTable_config*>(this) = o;
+ *static_cast<android::ResTable_config*>(this) = o;
}
inline ConfigDescription::ConfigDescription(ConfigDescription&& o) {
- *this = o;
+ *this = o;
}
-inline ConfigDescription& ConfigDescription::operator=(const android::ResTable_config& o) {
- *static_cast<android::ResTable_config*>(this) = o;
- size = sizeof(android::ResTable_config);
- return *this;
+inline ConfigDescription& ConfigDescription::operator=(
+ const android::ResTable_config& o) {
+ *static_cast<android::ResTable_config*>(this) = o;
+ size = sizeof(android::ResTable_config);
+ return *this;
}
-inline ConfigDescription& ConfigDescription::operator=(const ConfigDescription& o) {
- *static_cast<android::ResTable_config*>(this) = o;
- return *this;
+inline ConfigDescription& ConfigDescription::operator=(
+ const ConfigDescription& o) {
+ *static_cast<android::ResTable_config*>(this) = o;
+ return *this;
}
inline ConfigDescription& ConfigDescription::operator=(ConfigDescription&& o) {
- *this = o;
- return *this;
+ *this = o;
+ return *this;
}
-inline bool ConfigDescription::matchWithDensity(const ConfigDescription& o) const {
- return match(o) && (density == 0 || density == o.density);
+inline bool ConfigDescription::matchWithDensity(
+ const ConfigDescription& o) const {
+ return match(o) && (density == 0 || density == o.density);
}
inline bool ConfigDescription::operator<(const ConfigDescription& o) const {
- return compare(o) < 0;
+ return compare(o) < 0;
}
inline bool ConfigDescription::operator<=(const ConfigDescription& o) const {
- return compare(o) <= 0;
+ return compare(o) <= 0;
}
inline bool ConfigDescription::operator==(const ConfigDescription& o) const {
- return compare(o) == 0;
+ return compare(o) == 0;
}
inline bool ConfigDescription::operator!=(const ConfigDescription& o) const {
- return compare(o) != 0;
+ return compare(o) != 0;
}
inline bool ConfigDescription::operator>=(const ConfigDescription& o) const {
- return compare(o) >= 0;
+ return compare(o) >= 0;
}
inline bool ConfigDescription::operator>(const ConfigDescription& o) const {
- return compare(o) > 0;
+ return compare(o) > 0;
}
-inline ::std::ostream& operator<<(::std::ostream& out, const ConfigDescription& o) {
- return out << o.toString().string();
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const ConfigDescription& o) {
+ return out << o.toString().string();
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_CONFIG_DESCRIPTION_H
+#endif // AAPT_CONFIG_DESCRIPTION_H
diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp
index 455a57f..66b7d47 100644
--- a/tools/aapt2/ConfigDescription_test.cpp
+++ b/tools/aapt2/ConfigDescription_test.cpp
@@ -23,76 +23,79 @@
namespace aapt {
-static ::testing::AssertionResult TestParse(const StringPiece& input,
- ConfigDescription* config = nullptr) {
- if (ConfigDescription::parse(input, config)) {
- return ::testing::AssertionSuccess() << input << " was successfully parsed";
- }
- return ::testing::AssertionFailure() << input << " could not be parsed";
+static ::testing::AssertionResult TestParse(
+ const StringPiece& input, ConfigDescription* config = nullptr) {
+ if (ConfigDescription::parse(input, config)) {
+ return ::testing::AssertionSuccess() << input << " was successfully parsed";
+ }
+ return ::testing::AssertionFailure() << input << " could not be parsed";
}
TEST(ConfigDescriptionTest, ParseFailWhenQualifiersAreOutOfOrder) {
- EXPECT_FALSE(TestParse("en-sw600dp-ldrtl"));
- EXPECT_FALSE(TestParse("land-en"));
- EXPECT_FALSE(TestParse("hdpi-320dpi"));
+ EXPECT_FALSE(TestParse("en-sw600dp-ldrtl"));
+ EXPECT_FALSE(TestParse("land-en"));
+ EXPECT_FALSE(TestParse("hdpi-320dpi"));
}
TEST(ConfigDescriptionTest, ParseFailWhenQualifiersAreNotMatched) {
- EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL"));
+ EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL"));
}
TEST(ConfigDescriptionTest, ParseFailWhenQualifiersHaveTrailingDash) {
- EXPECT_FALSE(TestParse("en-sw600dp-land-"));
+ EXPECT_FALSE(TestParse("en-sw600dp-land-"));
}
TEST(ConfigDescriptionTest, ParseBasicQualifiers) {
- ConfigDescription config;
- EXPECT_TRUE(TestParse("", &config));
- EXPECT_EQ(std::string(""), config.toString().string());
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("", &config));
+ EXPECT_EQ(std::string(""), config.toString().string());
- EXPECT_TRUE(TestParse("fr-land", &config));
- EXPECT_EQ(std::string("fr-land"), config.toString().string());
+ EXPECT_TRUE(TestParse("fr-land", &config));
+ EXPECT_EQ(std::string("fr-land"), config.toString().string());
- EXPECT_TRUE(TestParse("mcc310-pl-sw720dp-normal-long-port-night-"
- "xhdpi-keyssoft-qwerty-navexposed-nonav", &config));
- EXPECT_EQ(std::string("mcc310-pl-sw720dp-normal-long-port-night-"
- "xhdpi-keyssoft-qwerty-navexposed-nonav-v13"), config.toString().string());
+ EXPECT_TRUE(
+ TestParse("mcc310-pl-sw720dp-normal-long-port-night-"
+ "xhdpi-keyssoft-qwerty-navexposed-nonav",
+ &config));
+ EXPECT_EQ(std::string("mcc310-pl-sw720dp-normal-long-port-night-"
+ "xhdpi-keyssoft-qwerty-navexposed-nonav-v13"),
+ config.toString().string());
}
TEST(ConfigDescriptionTest, ParseLocales) {
- ConfigDescription config;
- EXPECT_TRUE(TestParse("en-rUS", &config));
- EXPECT_EQ(std::string("en-rUS"), config.toString().string());
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("en-rUS", &config));
+ EXPECT_EQ(std::string("en-rUS"), config.toString().string());
}
TEST(ConfigDescriptionTest, ParseQualifierAddedInApi13) {
- ConfigDescription config;
- EXPECT_TRUE(TestParse("sw600dp", &config));
- EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("sw600dp", &config));
+ EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
- EXPECT_TRUE(TestParse("sw600dp-v8", &config));
- EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
+ EXPECT_TRUE(TestParse("sw600dp-v8", &config));
+ EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
}
TEST(ConfigDescriptionTest, ParseCarAttribute) {
- ConfigDescription config;
- EXPECT_TRUE(TestParse("car", &config));
- EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_CAR, config.uiMode);
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("car", &config));
+ EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_CAR, config.uiMode);
}
TEST(ConfigDescriptionTest, TestParsingRoundQualifier) {
- ConfigDescription config;
- EXPECT_TRUE(TestParse("round", &config));
- EXPECT_EQ(android::ResTable_config::SCREENROUND_YES,
- config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
- EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
- EXPECT_EQ(std::string("round-v23"), config.toString().string());
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("round", &config));
+ EXPECT_EQ(android::ResTable_config::SCREENROUND_YES,
+ config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
+ EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
+ EXPECT_EQ(std::string("round-v23"), config.toString().string());
- EXPECT_TRUE(TestParse("notround", &config));
- EXPECT_EQ(android::ResTable_config::SCREENROUND_NO,
- config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
- EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
- EXPECT_EQ(std::string("notround-v23"), config.toString().string());
+ EXPECT_TRUE(TestParse("notround", &config));
+ EXPECT_EQ(android::ResTable_config::SCREENROUND_NO,
+ config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
+ EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
+ EXPECT_EQ(std::string("notround-v23"), config.toString().string());
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 304e571..965db9e 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -17,8 +17,8 @@
#include "Debug.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
-#include "util/Util.h"
#include "ValueVisitor.h"
+#include "util/Util.h"
#include <algorithm>
#include <iostream>
@@ -31,275 +31,279 @@
namespace aapt {
class PrintVisitor : public ValueVisitor {
-public:
- using ValueVisitor::visit;
+ public:
+ using ValueVisitor::visit;
- void visit(Attribute* attr) override {
- std::cout << "(attr) type=";
- attr->printMask(&std::cout);
- static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM |
- android::ResTable_map::TYPE_FLAGS;
- if (attr->typeMask & kMask) {
- for (const auto& symbol : attr->symbols) {
- std::cout << "\n " << symbol.symbol.name.value().entry;
- if (symbol.symbol.id) {
- std::cout << " (" << symbol.symbol.id.value() << ")";
- }
- std::cout << " = " << symbol.value;
- }
+ void visit(Attribute* attr) override {
+ std::cout << "(attr) type=";
+ attr->printMask(&std::cout);
+ static constexpr uint32_t kMask =
+ android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS;
+ if (attr->typeMask & kMask) {
+ for (const auto& symbol : attr->symbols) {
+ std::cout << "\n " << symbol.symbol.name.value().entry;
+ if (symbol.symbol.id) {
+ std::cout << " (" << symbol.symbol.id.value() << ")";
}
+ std::cout << " = " << symbol.value;
+ }
}
+ }
- void visit(Style* style) override {
- std::cout << "(style)";
- if (style->parent) {
- const Reference& parentRef = style->parent.value();
- std::cout << " parent=";
- if (parentRef.name) {
- if (parentRef.privateReference) {
- std::cout << "*";
- }
- std::cout << parentRef.name.value() << " ";
- }
-
- if (parentRef.id) {
- std::cout << parentRef.id.value();
- }
+ void visit(Style* style) override {
+ std::cout << "(style)";
+ if (style->parent) {
+ const Reference& parentRef = style->parent.value();
+ std::cout << " parent=";
+ if (parentRef.name) {
+ if (parentRef.privateReference) {
+ std::cout << "*";
}
+ std::cout << parentRef.name.value() << " ";
+ }
- for (const auto& entry : style->entries) {
- std::cout << "\n ";
- if (entry.key.name) {
- const ResourceName& name = entry.key.name.value();
- if (!name.package.empty()) {
- std::cout << name.package << ":";
- }
- std::cout << name.entry;
- }
+ if (parentRef.id) {
+ std::cout << parentRef.id.value();
+ }
+ }
- if (entry.key.id) {
- std::cout << "(" << entry.key.id.value() << ")";
- }
-
- std::cout << "=" << *entry.value;
+ for (const auto& entry : style->entries) {
+ std::cout << "\n ";
+ if (entry.key.name) {
+ const ResourceName& name = entry.key.name.value();
+ if (!name.package.empty()) {
+ std::cout << name.package << ":";
}
+ std::cout << name.entry;
+ }
+
+ if (entry.key.id) {
+ std::cout << "(" << entry.key.id.value() << ")";
+ }
+
+ std::cout << "=" << *entry.value;
}
+ }
- void visit(Array* array) override {
- array->print(&std::cout);
- }
+ void visit(Array* array) override { array->print(&std::cout); }
- void visit(Plural* plural) override {
- plural->print(&std::cout);
- }
+ void visit(Plural* plural) override { plural->print(&std::cout); }
- void visit(Styleable* styleable) override {
- std::cout << "(styleable)";
- for (const auto& attr : styleable->entries) {
- std::cout << "\n ";
- if (attr.name) {
- const ResourceName& name = attr.name.value();
- if (!name.package.empty()) {
- std::cout << name.package << ":";
- }
- std::cout << name.entry;
- }
-
- if (attr.id) {
- std::cout << "(" << attr.id.value() << ")";
- }
+ void visit(Styleable* styleable) override {
+ std::cout << "(styleable)";
+ for (const auto& attr : styleable->entries) {
+ std::cout << "\n ";
+ if (attr.name) {
+ const ResourceName& name = attr.name.value();
+ if (!name.package.empty()) {
+ std::cout << name.package << ":";
}
- }
+ std::cout << name.entry;
+ }
- void visitItem(Item* item) override {
- item->print(&std::cout);
+ if (attr.id) {
+ std::cout << "(" << attr.id.value() << ")";
+ }
}
+ }
+
+ void visitItem(Item* item) override { item->print(&std::cout); }
};
-void Debug::printTable(ResourceTable* table, const DebugPrintTableOptions& options) {
- PrintVisitor visitor;
+void Debug::printTable(ResourceTable* table,
+ const DebugPrintTableOptions& options) {
+ PrintVisitor visitor;
- for (auto& package : table->packages) {
- std::cout << "Package name=" << package->name;
- if (package->id) {
- std::cout << " id=" << std::hex << (int) package->id.value() << std::dec;
+ for (auto& package : table->packages) {
+ std::cout << "Package name=" << package->name;
+ if (package->id) {
+ std::cout << " id=" << std::hex << (int)package->id.value() << std::dec;
+ }
+ std::cout << std::endl;
+
+ for (const auto& type : package->types) {
+ std::cout << "\n type " << type->type;
+ if (type->id) {
+ std::cout << " id=" << std::hex << (int)type->id.value() << std::dec;
+ }
+ std::cout << " entryCount=" << type->entries.size() << std::endl;
+
+ std::vector<const ResourceEntry*> sortedEntries;
+ for (const auto& entry : type->entries) {
+ auto iter = std::lower_bound(
+ sortedEntries.begin(), sortedEntries.end(), entry.get(),
+ [](const ResourceEntry* a, const ResourceEntry* b) -> bool {
+ if (a->id && b->id) {
+ return a->id.value() < b->id.value();
+ } else if (a->id) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+ sortedEntries.insert(iter, entry.get());
+ }
+
+ for (const ResourceEntry* entry : sortedEntries) {
+ ResourceId id(package->id ? package->id.value() : uint8_t(0),
+ type->id ? type->id.value() : uint8_t(0),
+ entry->id ? entry->id.value() : uint16_t(0));
+ ResourceName name(package->name, type->type, entry->name);
+
+ std::cout << " spec resource " << id << " " << name;
+ switch (entry->symbolStatus.state) {
+ case SymbolState::kPublic:
+ std::cout << " PUBLIC";
+ break;
+ case SymbolState::kPrivate:
+ std::cout << " _PRIVATE_";
+ break;
+ default:
+ break;
}
+
std::cout << std::endl;
- for (const auto& type : package->types) {
- std::cout << "\n type " << type->type;
- if (type->id) {
- std::cout << " id=" << std::hex << (int) type->id.value() << std::dec;
- }
- std::cout << " entryCount=" << type->entries.size() << std::endl;
-
- std::vector<const ResourceEntry*> sortedEntries;
- for (const auto& entry : type->entries) {
- auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(),
- [](const ResourceEntry* a, const ResourceEntry* b) -> bool {
- if (a->id && b->id) {
- return a->id.value() < b->id.value();
- } else if (a->id) {
- return true;
- } else {
- return false;
- }
- });
- sortedEntries.insert(iter, entry.get());
- }
-
- for (const ResourceEntry* entry : sortedEntries) {
- ResourceId id(package->id ? package->id.value() : uint8_t(0),
- type->id ? type->id.value() : uint8_t(0),
- entry->id ? entry->id.value() : uint16_t(0));
- ResourceName name(package->name, type->type, entry->name);
-
- std::cout << " spec resource " << id << " " << name;
- switch (entry->symbolStatus.state) {
- case SymbolState::kPublic: std::cout << " PUBLIC"; break;
- case SymbolState::kPrivate: std::cout << " _PRIVATE_"; break;
- default: break;
- }
-
- std::cout << std::endl;
-
- for (const auto& value : entry->values) {
- std::cout << " (" << value->config << ") ";
- value->value->accept(&visitor);
- if (options.showSources && !value->value->getSource().path.empty()) {
- std::cout << " src=" << value->value->getSource();
- }
- std::cout << std::endl;
- }
- }
+ for (const auto& value : entry->values) {
+ std::cout << " (" << value->config << ") ";
+ value->value->accept(&visitor);
+ if (options.showSources && !value->value->getSource().path.empty()) {
+ std::cout << " src=" << value->value->getSource();
+ }
+ std::cout << std::endl;
}
+ }
}
+ }
}
-static size_t getNodeIndex(const std::vector<ResourceName>& names, const ResourceName& name) {
- auto iter = std::lower_bound(names.begin(), names.end(), name);
- assert(iter != names.end() && *iter == name);
- return std::distance(names.begin(), iter);
+static size_t getNodeIndex(const std::vector<ResourceName>& names,
+ const ResourceName& name) {
+ auto iter = std::lower_bound(names.begin(), names.end(), name);
+ assert(iter != names.end() && *iter == name);
+ return std::distance(names.begin(), iter);
}
-void Debug::printStyleGraph(ResourceTable* table, const ResourceName& targetStyle) {
- std::map<ResourceName, std::set<ResourceName>> graph;
+void Debug::printStyleGraph(ResourceTable* table,
+ const ResourceName& targetStyle) {
+ std::map<ResourceName, std::set<ResourceName>> graph;
- std::queue<ResourceName> stylesToVisit;
- stylesToVisit.push(targetStyle);
- for (; !stylesToVisit.empty(); stylesToVisit.pop()) {
- const ResourceName& styleName = stylesToVisit.front();
- std::set<ResourceName>& parents = graph[styleName];
- if (!parents.empty()) {
- // We've already visited this style.
- continue;
+ std::queue<ResourceName> stylesToVisit;
+ stylesToVisit.push(targetStyle);
+ for (; !stylesToVisit.empty(); stylesToVisit.pop()) {
+ const ResourceName& styleName = stylesToVisit.front();
+ std::set<ResourceName>& parents = graph[styleName];
+ if (!parents.empty()) {
+ // We've already visited this style.
+ continue;
+ }
+
+ Maybe<ResourceTable::SearchResult> result = table->findResource(styleName);
+ if (result) {
+ ResourceEntry* entry = result.value().entry;
+ for (const auto& value : entry->values) {
+ if (Style* style = valueCast<Style>(value->value.get())) {
+ if (style->parent && style->parent.value().name) {
+ parents.insert(style->parent.value().name.value());
+ stylesToVisit.push(style->parent.value().name.value());
+ }
}
-
- Maybe<ResourceTable::SearchResult> result = table->findResource(styleName);
- if (result) {
- ResourceEntry* entry = result.value().entry;
- for (const auto& value : entry->values) {
- if (Style* style = valueCast<Style>(value->value.get())) {
- if (style->parent && style->parent.value().name) {
- parents.insert(style->parent.value().name.value());
- stylesToVisit.push(style->parent.value().name.value());
- }
- }
- }
- }
+ }
}
+ }
- std::vector<ResourceName> names;
- for (const auto& entry : graph) {
- names.push_back(entry.first);
+ std::vector<ResourceName> names;
+ for (const auto& entry : graph) {
+ names.push_back(entry.first);
+ }
+
+ std::cout << "digraph styles {\n";
+ for (const auto& name : names) {
+ std::cout << " node_" << getNodeIndex(names, name) << " [label=\"" << name
+ << "\"];\n";
+ }
+
+ for (const auto& entry : graph) {
+ const ResourceName& styleName = entry.first;
+ size_t styleNodeIndex = getNodeIndex(names, styleName);
+
+ for (const auto& parentName : entry.second) {
+ std::cout << " node_" << styleNodeIndex << " -> "
+ << "node_" << getNodeIndex(names, parentName) << ";\n";
}
+ }
- std::cout << "digraph styles {\n";
- for (const auto& name : names) {
- std::cout << " node_" << getNodeIndex(names, name)
- << " [label=\"" << name << "\"];\n";
- }
-
- for (const auto& entry : graph) {
- const ResourceName& styleName = entry.first;
- size_t styleNodeIndex = getNodeIndex(names, styleName);
-
- for (const auto& parentName : entry.second) {
- std::cout << " node_" << styleNodeIndex << " -> "
- << "node_" << getNodeIndex(names, parentName) << ";\n";
- }
- }
-
- std::cout << "}" << std::endl;
+ std::cout << "}" << std::endl;
}
void Debug::dumpHex(const void* data, size_t len) {
- const uint8_t* d = (const uint8_t*) data;
- for (size_t i = 0; i < len; i++) {
- std::cerr << std::hex << std::setfill('0') << std::setw(2) << (uint32_t) d[i] << " ";
- if (i % 8 == 7) {
- std::cerr << "\n";
- }
+ const uint8_t* d = (const uint8_t*)data;
+ for (size_t i = 0; i < len; i++) {
+ std::cerr << std::hex << std::setfill('0') << std::setw(2) << (uint32_t)d[i]
+ << " ";
+ if (i % 8 == 7) {
+ std::cerr << "\n";
}
+ }
- if (len - 1 % 8 != 7) {
- std::cerr << std::endl;
- }
+ if (len - 1 % 8 != 7) {
+ std::cerr << std::endl;
+ }
}
namespace {
class XmlPrinter : public xml::Visitor {
-public:
- using xml::Visitor::visit;
+ public:
+ using xml::Visitor::visit;
- void visit(xml::Element* el) override {
- std::cerr << mPrefix;
- std::cerr << "E: ";
- if (!el->namespaceUri.empty()) {
- std::cerr << el->namespaceUri << ":";
- }
- std::cerr << el->name << " (line=" << el->lineNumber << ")\n";
+ void visit(xml::Element* el) override {
+ std::cerr << mPrefix;
+ std::cerr << "E: ";
+ if (!el->namespaceUri.empty()) {
+ std::cerr << el->namespaceUri << ":";
+ }
+ std::cerr << el->name << " (line=" << el->lineNumber << ")\n";
- for (const xml::Attribute& attr : el->attributes) {
- std::cerr << mPrefix << " A: ";
- if (!attr.namespaceUri.empty()) {
- std::cerr << attr.namespaceUri << ":";
- }
- std::cerr << attr.name << "=" << attr.value << "\n";
- }
-
- const size_t previousSize = mPrefix.size();
- mPrefix += " ";
- xml::Visitor::visit(el);
- mPrefix.resize(previousSize);
+ for (const xml::Attribute& attr : el->attributes) {
+ std::cerr << mPrefix << " A: ";
+ if (!attr.namespaceUri.empty()) {
+ std::cerr << attr.namespaceUri << ":";
+ }
+ std::cerr << attr.name << "=" << attr.value << "\n";
}
- void visit(xml::Namespace* ns) override {
- std::cerr << mPrefix;
- std::cerr << "N: " << ns->namespacePrefix << "=" << ns->namespaceUri
- << " (line=" << ns->lineNumber << ")\n";
+ const size_t previousSize = mPrefix.size();
+ mPrefix += " ";
+ xml::Visitor::visit(el);
+ mPrefix.resize(previousSize);
+ }
- const size_t previousSize = mPrefix.size();
- mPrefix += " ";
- xml::Visitor::visit(ns);
- mPrefix.resize(previousSize);
- }
+ void visit(xml::Namespace* ns) override {
+ std::cerr << mPrefix;
+ std::cerr << "N: " << ns->namespacePrefix << "=" << ns->namespaceUri
+ << " (line=" << ns->lineNumber << ")\n";
- void visit(xml::Text* text) override {
- std::cerr << mPrefix;
- std::cerr << "T: '" << text->text << "'\n";
- }
+ const size_t previousSize = mPrefix.size();
+ mPrefix += " ";
+ xml::Visitor::visit(ns);
+ mPrefix.resize(previousSize);
+ }
-private:
- std::string mPrefix;
+ void visit(xml::Text* text) override {
+ std::cerr << mPrefix;
+ std::cerr << "T: '" << text->text << "'\n";
+ }
+
+ private:
+ std::string mPrefix;
};
-} // namespace
+} // namespace
void Debug::dumpXml(xml::XmlResource* doc) {
- XmlPrinter printer;
- doc->root->accept(&printer);
+ XmlPrinter printer;
+ doc->root->accept(&printer);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index c0fcbf1..bd92ec1 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -27,17 +27,18 @@
namespace aapt {
struct DebugPrintTableOptions {
- bool showSources = false;
+ bool showSources = false;
};
struct Debug {
- static void printTable(ResourceTable* table, const DebugPrintTableOptions& options = {});
- static void printStyleGraph(ResourceTable* table,
- const ResourceName& targetStyle);
- static void dumpHex(const void* data, size_t len);
- static void dumpXml(xml::XmlResource* doc);
+ static void printTable(ResourceTable* table,
+ const DebugPrintTableOptions& options = {});
+ static void printStyleGraph(ResourceTable* table,
+ const ResourceName& targetStyle);
+ static void dumpHex(const void* data, size_t len);
+ static void dumpXml(xml::XmlResource* doc);
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_DEBUG_H
+#endif // AAPT_DEBUG_H
diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h
index 725027c..d39cf4c 100644
--- a/tools/aapt2/Diagnostics.h
+++ b/tools/aapt2/Diagnostics.h
@@ -29,119 +29,112 @@
namespace aapt {
struct DiagMessageActual {
- Source source;
- std::string message;
+ Source source;
+ std::string message;
};
struct DiagMessage {
-private:
- Source mSource;
- std::stringstream mMessage;
+ private:
+ Source mSource;
+ std::stringstream mMessage;
-public:
- DiagMessage() = default;
+ public:
+ DiagMessage() = default;
- explicit DiagMessage(const StringPiece& src) : mSource(src) {
- }
+ explicit DiagMessage(const StringPiece& src) : mSource(src) {}
- explicit DiagMessage(const Source& src) : mSource(src) {
- }
+ explicit DiagMessage(const Source& src) : mSource(src) {}
- explicit DiagMessage(size_t line) : mSource(Source().withLine(line)) {
- }
+ explicit DiagMessage(size_t line) : mSource(Source().withLine(line)) {}
- template <typename T>
- DiagMessage& operator<<(const T& value) {
- mMessage << value;
- return *this;
- }
+ template <typename T>
+ DiagMessage& operator<<(const T& value) {
+ mMessage << value;
+ return *this;
+ }
- DiagMessageActual build() const {
- return DiagMessageActual{ mSource, mMessage.str() };
- }
+ DiagMessageActual build() const {
+ return DiagMessageActual{mSource, mMessage.str()};
+ }
};
struct IDiagnostics {
- virtual ~IDiagnostics() = default;
+ virtual ~IDiagnostics() = default;
- enum class Level {
- Note,
- Warn,
- Error
- };
+ enum class Level { Note, Warn, Error };
- virtual void log(Level level, DiagMessageActual& actualMsg) = 0;
+ virtual void log(Level level, DiagMessageActual& actualMsg) = 0;
- virtual void error(const DiagMessage& message) {
- DiagMessageActual actual = message.build();
- log(Level::Error, actual);
- }
+ virtual void error(const DiagMessage& message) {
+ DiagMessageActual actual = message.build();
+ log(Level::Error, actual);
+ }
- virtual void warn(const DiagMessage& message) {
- DiagMessageActual actual = message.build();
- log(Level::Warn, actual);
- }
+ virtual void warn(const DiagMessage& message) {
+ DiagMessageActual actual = message.build();
+ log(Level::Warn, actual);
+ }
- virtual void note(const DiagMessage& message) {
- DiagMessageActual actual = message.build();
- log(Level::Note, actual);
- }
+ virtual void note(const DiagMessage& message) {
+ DiagMessageActual actual = message.build();
+ log(Level::Note, actual);
+ }
};
class StdErrDiagnostics : public IDiagnostics {
-public:
- StdErrDiagnostics() = default;
+ public:
+ StdErrDiagnostics() = default;
- void log(Level level, DiagMessageActual& actualMsg) override {
- const char* tag;
+ void log(Level level, DiagMessageActual& actualMsg) override {
+ const char* tag;
- switch (level) {
- case Level::Error:
- mNumErrors++;
- if (mNumErrors > 20) {
- return;
- }
- tag = "error";
- break;
-
- case Level::Warn:
- tag = "warn";
- break;
-
- case Level::Note:
- tag = "note";
- break;
+ switch (level) {
+ case Level::Error:
+ mNumErrors++;
+ if (mNumErrors > 20) {
+ return;
}
+ tag = "error";
+ break;
- if (!actualMsg.source.path.empty()) {
- std::cerr << actualMsg.source << ": ";
- }
- std::cerr << tag << ": " << actualMsg.message << "." << std::endl;
+ case Level::Warn:
+ tag = "warn";
+ break;
+
+ case Level::Note:
+ tag = "note";
+ break;
}
-private:
- size_t mNumErrors = 0;
+ if (!actualMsg.source.path.empty()) {
+ std::cerr << actualMsg.source << ": ";
+ }
+ std::cerr << tag << ": " << actualMsg.message << "." << std::endl;
+ }
- DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics);
+ private:
+ size_t mNumErrors = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics);
};
class SourcePathDiagnostics : public IDiagnostics {
-public:
- SourcePathDiagnostics(const Source& src, IDiagnostics* diag) : mSource(src), mDiag(diag) {
- }
+ public:
+ SourcePathDiagnostics(const Source& src, IDiagnostics* diag)
+ : mSource(src), mDiag(diag) {}
- void log(Level level, DiagMessageActual& actualMsg) override {
- actualMsg.source.path = mSource.path;
- mDiag->log(level, actualMsg);
- }
+ void log(Level level, DiagMessageActual& actualMsg) override {
+ actualMsg.source.path = mSource.path;
+ mDiag->log(level, actualMsg);
+ }
-private:
- Source mSource;
- IDiagnostics* mDiag;
+ private:
+ Source mSource;
+ IDiagnostics* mDiag;
- DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics);
+ DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics);
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_DIAGNOSTICS_H */
diff --git a/tools/aapt2/DominatorTree.cpp b/tools/aapt2/DominatorTree.cpp
index 29587a8..8f6f783 100644
--- a/tools/aapt2/DominatorTree.cpp
+++ b/tools/aapt2/DominatorTree.cpp
@@ -14,76 +14,78 @@
* limitations under the License.
*/
-#include "ConfigDescription.h"
#include "DominatorTree.h"
+#include "ConfigDescription.h"
#include <algorithm>
namespace aapt {
DominatorTree::DominatorTree(
- const std::vector<std::unique_ptr<ResourceConfigValue>>& configs) {
- for (const auto& config : configs) {
- mProductRoots[config->product].tryAddChild(
- util::make_unique<Node>(config.get(), nullptr));
- }
+ const std::vector<std::unique_ptr<ResourceConfigValue>>& configs) {
+ for (const auto& config : configs) {
+ mProductRoots[config->product].tryAddChild(
+ util::make_unique<Node>(config.get(), nullptr));
+ }
}
void DominatorTree::accept(Visitor* visitor) {
- for (auto& entry : mProductRoots) {
- visitor->visitTree(entry.first, &entry.second);
- }
+ for (auto& entry : mProductRoots) {
+ visitor->visitTree(entry.first, &entry.second);
+ }
}
bool DominatorTree::Node::tryAddChild(std::unique_ptr<Node> newChild) {
- assert(newChild->mValue && "cannot add a root or empty node as a child");
- if (mValue && !dominates(newChild.get())) {
- // This is not the root and the child dominates us.
- return false;
- }
- return addChild(std::move(newChild));
+ assert(newChild->mValue && "cannot add a root or empty node as a child");
+ if (mValue && !dominates(newChild.get())) {
+ // This is not the root and the child dominates us.
+ return false;
+ }
+ return addChild(std::move(newChild));
}
bool DominatorTree::Node::addChild(std::unique_ptr<Node> newChild) {
- bool hasDominatedChildren = false;
- // Demote children dominated by the new config.
- for (auto& child : mChildren) {
- if (newChild->dominates(child.get())) {
- child->mParent = newChild.get();
- newChild->mChildren.push_back(std::move(child));
- child = {};
- hasDominatedChildren = true;
- }
+ bool hasDominatedChildren = false;
+ // Demote children dominated by the new config.
+ for (auto& child : mChildren) {
+ if (newChild->dominates(child.get())) {
+ child->mParent = newChild.get();
+ newChild->mChildren.push_back(std::move(child));
+ child = {};
+ hasDominatedChildren = true;
}
- // Remove dominated children.
- if (hasDominatedChildren) {
- mChildren.erase(std::remove_if(mChildren.begin(), mChildren.end(),
- [](const std::unique_ptr<Node>& child) -> bool {
- return child == nullptr;
- }), mChildren.end());
+ }
+ // Remove dominated children.
+ if (hasDominatedChildren) {
+ mChildren.erase(
+ std::remove_if(mChildren.begin(), mChildren.end(),
+ [](const std::unique_ptr<Node>& child) -> bool {
+ return child == nullptr;
+ }),
+ mChildren.end());
+ }
+ // Add the new config to a child if a child dominates the new config.
+ for (auto& child : mChildren) {
+ if (child->dominates(newChild.get())) {
+ child->addChild(std::move(newChild));
+ return true;
}
- // Add the new config to a child if a child dominates the new config.
- for (auto& child : mChildren) {
- if (child->dominates(newChild.get())) {
- child->addChild(std::move(newChild));
- return true;
- }
- }
- // The new config is not dominated by a child, so add it here.
- newChild->mParent = this;
- mChildren.push_back(std::move(newChild));
- return true;
+ }
+ // The new config is not dominated by a child, so add it here.
+ newChild->mParent = this;
+ mChildren.push_back(std::move(newChild));
+ return true;
}
bool DominatorTree::Node::dominates(const Node* other) const {
- // Check root node dominations.
- if (other->isRootNode()) {
- return isRootNode();
- } else if (isRootNode()) {
- return true;
- }
- // Neither node is a root node; compare the configurations.
- return mValue->config.dominates(other->mValue->config);
+ // Check root node dominations.
+ if (other->isRootNode()) {
+ return isRootNode();
+ } else if (isRootNode()) {
+ return true;
+ }
+ // Neither node is a root node; compare the configurations.
+ return mValue->config.dominates(other->mValue->config);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/DominatorTree.h b/tools/aapt2/DominatorTree.h
index ad2df0e..6d45b5d 100644
--- a/tools/aapt2/DominatorTree.h
+++ b/tools/aapt2/DominatorTree.h
@@ -46,82 +46,76 @@
* will exhibit undefined behavior.
*/
class DominatorTree {
-public:
- explicit DominatorTree(const std::vector<std::unique_ptr<ResourceConfigValue>>& configs);
+ public:
+ explicit DominatorTree(
+ const std::vector<std::unique_ptr<ResourceConfigValue>>& configs);
- class Node {
- public:
- explicit Node(ResourceConfigValue* value = nullptr, Node* parent = nullptr) :
- mValue(value), mParent(parent) {
- }
+ class Node {
+ public:
+ explicit Node(ResourceConfigValue* value = nullptr, Node* parent = nullptr)
+ : mValue(value), mParent(parent) {}
- inline ResourceConfigValue* value() const {
- return mValue;
- }
+ inline ResourceConfigValue* value() const { return mValue; }
- inline Node* parent() const {
- return mParent;
- }
+ inline Node* parent() const { return mParent; }
- inline bool isRootNode() const {
- return !mValue;
- }
+ inline bool isRootNode() const { return !mValue; }
- inline const std::vector<std::unique_ptr<Node>>& children() const {
- return mChildren;
- }
-
- bool tryAddChild(std::unique_ptr<Node> newChild);
-
- private:
- bool addChild(std::unique_ptr<Node> newChild);
- bool dominates(const Node* other) const;
-
- ResourceConfigValue* mValue;
- Node* mParent;
- std::vector<std::unique_ptr<Node>> mChildren;
-
- DISALLOW_COPY_AND_ASSIGN(Node);
- };
-
- struct Visitor {
- virtual ~Visitor() = default;
- virtual void visitTree(const std::string& product, Node* root) = 0;
- };
-
- class BottomUpVisitor : public Visitor {
- public:
- virtual ~BottomUpVisitor() = default;
-
- void visitTree(const std::string& product, Node* root) override {
- for (auto& child : root->children()) {
- visitNode(child.get());
- }
- }
-
- virtual void visitConfig(Node* node) = 0;
-
- private:
- void visitNode(Node* node) {
- for (auto& child : node->children()) {
- visitNode(child.get());
- }
- visitConfig(node);
- }
- };
-
- void accept(Visitor* visitor);
-
- inline const std::map<std::string, Node>& getProductRoots() const {
- return mProductRoots;
+ inline const std::vector<std::unique_ptr<Node>>& children() const {
+ return mChildren;
}
-private:
- DISALLOW_COPY_AND_ASSIGN(DominatorTree);
+ bool tryAddChild(std::unique_ptr<Node> newChild);
- std::map<std::string, Node> mProductRoots;
+ private:
+ bool addChild(std::unique_ptr<Node> newChild);
+ bool dominates(const Node* other) const;
+
+ ResourceConfigValue* mValue;
+ Node* mParent;
+ std::vector<std::unique_ptr<Node>> mChildren;
+
+ DISALLOW_COPY_AND_ASSIGN(Node);
+ };
+
+ struct Visitor {
+ virtual ~Visitor() = default;
+ virtual void visitTree(const std::string& product, Node* root) = 0;
+ };
+
+ class BottomUpVisitor : public Visitor {
+ public:
+ virtual ~BottomUpVisitor() = default;
+
+ void visitTree(const std::string& product, Node* root) override {
+ for (auto& child : root->children()) {
+ visitNode(child.get());
+ }
+ }
+
+ virtual void visitConfig(Node* node) = 0;
+
+ private:
+ void visitNode(Node* node) {
+ for (auto& child : node->children()) {
+ visitNode(child.get());
+ }
+ visitConfig(node);
+ }
+ };
+
+ void accept(Visitor* visitor);
+
+ inline const std::map<std::string, Node>& getProductRoots() const {
+ return mProductRoots;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DominatorTree);
+
+ std::map<std::string, Node> mProductRoots;
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_DOMINATOR_TREE_H
+#endif // AAPT_DOMINATOR_TREE_H
diff --git a/tools/aapt2/DominatorTree_test.cpp b/tools/aapt2/DominatorTree_test.cpp
index fb850e4..a42d2f7 100644
--- a/tools/aapt2/DominatorTree_test.cpp
+++ b/tools/aapt2/DominatorTree_test.cpp
@@ -27,125 +27,132 @@
namespace {
class PrettyPrinter : public DominatorTree::Visitor {
-public:
- explicit PrettyPrinter(const int indent = 2) : mIndent(indent) {
- }
+ public:
+ explicit PrettyPrinter(const int indent = 2) : mIndent(indent) {}
- void visitTree(const std::string& product, DominatorTree::Node* root) override {
- for (auto& child : root->children()) {
- visitNode(child.get(), 0);
- }
+ void visitTree(const std::string& product,
+ DominatorTree::Node* root) override {
+ for (auto& child : root->children()) {
+ visitNode(child.get(), 0);
}
+ }
- std::string toString(DominatorTree* tree) {
- mBuffer.str("");
- mBuffer.clear();
- tree->accept(this);
- return mBuffer.str();
+ std::string toString(DominatorTree* tree) {
+ mBuffer.str("");
+ mBuffer.clear();
+ tree->accept(this);
+ return mBuffer.str();
+ }
+
+ private:
+ void visitConfig(const DominatorTree::Node* node, const int indent) {
+ auto configString = node->value()->config.toString();
+ mBuffer << std::string(indent, ' ')
+ << (configString.isEmpty() ? "<default>" : configString)
+ << std::endl;
+ }
+
+ void visitNode(const DominatorTree::Node* node, const int indent) {
+ visitConfig(node, indent);
+ for (const auto& child : node->children()) {
+ visitNode(child.get(), indent + mIndent);
}
+ }
-private:
- void visitConfig(const DominatorTree::Node* node, const int indent) {
- auto configString = node->value()->config.toString();
- mBuffer << std::string(indent, ' ')
- << (configString.isEmpty() ? "<default>" : configString)
- << std::endl;
- }
-
- void visitNode(const DominatorTree::Node* node, const int indent) {
- visitConfig(node, indent);
- for (const auto& child : node->children()) {
- visitNode(child.get(), indent + mIndent);
- }
- }
-
- std::stringstream mBuffer;
- const int mIndent = 2;
+ std::stringstream mBuffer;
+ const int mIndent = 2;
};
-} // namespace
+} // namespace
TEST(DominatorTreeTest, DefaultDominatesEverything) {
- const ConfigDescription defaultConfig = {};
- const ConfigDescription landConfig = test::parseConfigOrDie("land");
- const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land-v13");
+ const ConfigDescription defaultConfig = {};
+ const ConfigDescription landConfig = test::parseConfigOrDie("land");
+ const ConfigDescription sw600dpLandConfig =
+ test::parseConfigOrDie("sw600dp-land-v13");
- std::vector<std::unique_ptr<ResourceConfigValue>> configs;
- configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
- configs.push_back(util::make_unique<ResourceConfigValue>(landConfig, ""));
- configs.push_back(util::make_unique<ResourceConfigValue>(sw600dpLandConfig, ""));
+ std::vector<std::unique_ptr<ResourceConfigValue>> configs;
+ configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(landConfig, ""));
+ configs.push_back(
+ util::make_unique<ResourceConfigValue>(sw600dpLandConfig, ""));
- DominatorTree tree(configs);
- PrettyPrinter printer;
+ DominatorTree tree(configs);
+ PrettyPrinter printer;
- std::string expected =
- "<default>\n"
- " land\n"
- " sw600dp-land-v13\n";
- EXPECT_EQ(expected, printer.toString(&tree));
+ std::string expected =
+ "<default>\n"
+ " land\n"
+ " sw600dp-land-v13\n";
+ EXPECT_EQ(expected, printer.toString(&tree));
}
TEST(DominatorTreeTest, ProductsAreDominatedSeparately) {
- const ConfigDescription defaultConfig = {};
- const ConfigDescription landConfig = test::parseConfigOrDie("land");
- const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land-v13");
+ const ConfigDescription defaultConfig = {};
+ const ConfigDescription landConfig = test::parseConfigOrDie("land");
+ const ConfigDescription sw600dpLandConfig =
+ test::parseConfigOrDie("sw600dp-land-v13");
- std::vector<std::unique_ptr<ResourceConfigValue>> configs;
- configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
- configs.push_back(util::make_unique<ResourceConfigValue>(landConfig, ""));
- configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, "phablet"));
- configs.push_back(util::make_unique<ResourceConfigValue>(sw600dpLandConfig, "phablet"));
+ std::vector<std::unique_ptr<ResourceConfigValue>> configs;
+ configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(landConfig, ""));
+ configs.push_back(
+ util::make_unique<ResourceConfigValue>(defaultConfig, "phablet"));
+ configs.push_back(
+ util::make_unique<ResourceConfigValue>(sw600dpLandConfig, "phablet"));
- DominatorTree tree(configs);
- PrettyPrinter printer;
+ DominatorTree tree(configs);
+ PrettyPrinter printer;
- std::string expected =
- "<default>\n"
- " land\n"
- "<default>\n"
- " sw600dp-land-v13\n";
- EXPECT_EQ(expected, printer.toString(&tree));
+ std::string expected =
+ "<default>\n"
+ " land\n"
+ "<default>\n"
+ " sw600dp-land-v13\n";
+ EXPECT_EQ(expected, printer.toString(&tree));
}
TEST(DominatorTreeTest, MoreSpecificConfigurationsAreDominated) {
- const ConfigDescription defaultConfig = {};
- const ConfigDescription enConfig = test::parseConfigOrDie("en");
- const ConfigDescription enV21Config = test::parseConfigOrDie("en-v21");
- const ConfigDescription ldrtlConfig = test::parseConfigOrDie("ldrtl-v4");
- const ConfigDescription ldrtlXhdpiConfig = test::parseConfigOrDie("ldrtl-xhdpi-v4");
- const ConfigDescription sw300dpConfig = test::parseConfigOrDie("sw300dp-v13");
- const ConfigDescription sw540dpConfig = test::parseConfigOrDie("sw540dp-v14");
- const ConfigDescription sw600dpConfig = test::parseConfigOrDie("sw600dp-v14");
- const ConfigDescription sw720dpConfig = test::parseConfigOrDie("sw720dp-v13");
- const ConfigDescription v20Config = test::parseConfigOrDie("v20");
+ const ConfigDescription defaultConfig = {};
+ const ConfigDescription enConfig = test::parseConfigOrDie("en");
+ const ConfigDescription enV21Config = test::parseConfigOrDie("en-v21");
+ const ConfigDescription ldrtlConfig = test::parseConfigOrDie("ldrtl-v4");
+ const ConfigDescription ldrtlXhdpiConfig =
+ test::parseConfigOrDie("ldrtl-xhdpi-v4");
+ const ConfigDescription sw300dpConfig = test::parseConfigOrDie("sw300dp-v13");
+ const ConfigDescription sw540dpConfig = test::parseConfigOrDie("sw540dp-v14");
+ const ConfigDescription sw600dpConfig = test::parseConfigOrDie("sw600dp-v14");
+ const ConfigDescription sw720dpConfig = test::parseConfigOrDie("sw720dp-v13");
+ const ConfigDescription v20Config = test::parseConfigOrDie("v20");
- std::vector<std::unique_ptr<ResourceConfigValue>> configs;
- configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
- configs.push_back(util::make_unique<ResourceConfigValue>(enConfig, ""));
- configs.push_back(util::make_unique<ResourceConfigValue>(enV21Config, ""));
- configs.push_back(util::make_unique<ResourceConfigValue>(ldrtlConfig, ""));
- configs.push_back(util::make_unique<ResourceConfigValue>(ldrtlXhdpiConfig, ""));
- configs.push_back(util::make_unique<ResourceConfigValue>(sw300dpConfig, ""));
- configs.push_back(util::make_unique<ResourceConfigValue>(sw540dpConfig, ""));
- configs.push_back(util::make_unique<ResourceConfigValue>(sw600dpConfig, ""));
- configs.push_back(util::make_unique<ResourceConfigValue>(sw720dpConfig, ""));
- configs.push_back(util::make_unique<ResourceConfigValue>(v20Config, ""));
+ std::vector<std::unique_ptr<ResourceConfigValue>> configs;
+ configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(enConfig, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(enV21Config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(ldrtlConfig, ""));
+ configs.push_back(
+ util::make_unique<ResourceConfigValue>(ldrtlXhdpiConfig, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(sw300dpConfig, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(sw540dpConfig, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(sw600dpConfig, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(sw720dpConfig, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(v20Config, ""));
- DominatorTree tree(configs);
- PrettyPrinter printer;
+ DominatorTree tree(configs);
+ PrettyPrinter printer;
- std::string expected =
- "<default>\n"
- " en\n"
- " en-v21\n"
- " ldrtl-v4\n"
- " ldrtl-xhdpi-v4\n"
- " sw300dp-v13\n"
- " sw540dp-v14\n"
- " sw600dp-v14\n"
- " sw720dp-v13\n"
- " v20\n";
- EXPECT_EQ(expected, printer.toString(&tree));
+ std::string expected =
+ "<default>\n"
+ " en\n"
+ " en-v21\n"
+ " ldrtl-v4\n"
+ " ldrtl-xhdpi-v4\n"
+ " sw300dp-v13\n"
+ " sw540dp-v14\n"
+ " sw600dp-v14\n"
+ " sw720dp-v13\n"
+ " v20\n";
+ EXPECT_EQ(expected, printer.toString(&tree));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/Flags.cpp
index 3731ac7..cb16196 100644
--- a/tools/aapt2/Flags.cpp
+++ b/tools/aapt2/Flags.cpp
@@ -25,155 +25,167 @@
namespace aapt {
-Flags& Flags::requiredFlag(const StringPiece& name, const StringPiece& description,
- std::string* value) {
- auto func = [value](const StringPiece& arg) -> bool {
- *value = arg.toString();
- return true;
- };
+Flags& Flags::requiredFlag(const StringPiece& name,
+ const StringPiece& description, std::string* value) {
+ auto func = [value](const StringPiece& arg) -> bool {
+ *value = arg.toString();
+ return true;
+ };
- mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false});
- return *this;
+ mFlags.push_back(
+ Flag{name.toString(), description.toString(), func, true, 1, false});
+ return *this;
}
-Flags& Flags::requiredFlagList(const StringPiece& name, const StringPiece& description,
+Flags& Flags::requiredFlagList(const StringPiece& name,
+ const StringPiece& description,
std::vector<std::string>* value) {
- auto func = [value](const StringPiece& arg) -> bool {
- value->push_back(arg.toString());
- return true;
- };
+ auto func = [value](const StringPiece& arg) -> bool {
+ value->push_back(arg.toString());
+ return true;
+ };
- mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false });
- return *this;
+ mFlags.push_back(
+ Flag{name.toString(), description.toString(), func, true, 1, false});
+ return *this;
}
-Flags& Flags::optionalFlag(const StringPiece& name, const StringPiece& description,
+Flags& Flags::optionalFlag(const StringPiece& name,
+ const StringPiece& description,
Maybe<std::string>* value) {
- auto func = [value](const StringPiece& arg) -> bool {
- *value = arg.toString();
- return true;
- };
+ auto func = [value](const StringPiece& arg) -> bool {
+ *value = arg.toString();
+ return true;
+ };
- mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
- return *this;
+ mFlags.push_back(
+ Flag{name.toString(), description.toString(), func, false, 1, false});
+ return *this;
}
-Flags& Flags::optionalFlagList(const StringPiece& name, const StringPiece& description,
+Flags& Flags::optionalFlagList(const StringPiece& name,
+ const StringPiece& description,
std::vector<std::string>* value) {
- auto func = [value](const StringPiece& arg) -> bool {
- value->push_back(arg.toString());
- return true;
- };
+ auto func = [value](const StringPiece& arg) -> bool {
+ value->push_back(arg.toString());
+ return true;
+ };
- mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
- return *this;
+ mFlags.push_back(
+ Flag{name.toString(), description.toString(), func, false, 1, false});
+ return *this;
}
-Flags& Flags::optionalFlagList(const StringPiece& name, const StringPiece& description,
+Flags& Flags::optionalFlagList(const StringPiece& name,
+ const StringPiece& description,
std::unordered_set<std::string>* value) {
- auto func = [value](const StringPiece& arg) -> bool {
- value->insert(arg.toString());
- return true;
- };
+ auto func = [value](const StringPiece& arg) -> bool {
+ value->insert(arg.toString());
+ return true;
+ };
- mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
- return *this;
+ mFlags.push_back(
+ Flag{name.toString(), description.toString(), func, false, 1, false});
+ return *this;
}
-Flags& Flags::optionalSwitch(const StringPiece& name, const StringPiece& description,
- bool* value) {
- auto func = [value](const StringPiece& arg) -> bool {
- *value = true;
- return true;
- };
+Flags& Flags::optionalSwitch(const StringPiece& name,
+ const StringPiece& description, bool* value) {
+ auto func = [value](const StringPiece& arg) -> bool {
+ *value = true;
+ return true;
+ };
- mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 0, false });
- return *this;
+ mFlags.push_back(
+ Flag{name.toString(), description.toString(), func, false, 0, false});
+ return *this;
}
void Flags::usage(const StringPiece& command, std::ostream* out) {
- constexpr size_t kWidth = 50;
+ constexpr size_t kWidth = 50;
- *out << command << " [options]";
- for (const Flag& flag : mFlags) {
- if (flag.required) {
- *out << " " << flag.name << " arg";
- }
+ *out << command << " [options]";
+ for (const Flag& flag : mFlags) {
+ if (flag.required) {
+ *out << " " << flag.name << " arg";
+ }
+ }
+
+ *out << " files...\n\nOptions:\n";
+
+ for (const Flag& flag : mFlags) {
+ std::string argLine = flag.name;
+ if (flag.numArgs > 0) {
+ argLine += " arg";
}
- *out << " files...\n\nOptions:\n";
-
- for (const Flag& flag : mFlags) {
- std::string argLine = flag.name;
- if (flag.numArgs > 0) {
- argLine += " arg";
- }
-
- // Split the description by newlines and write out the argument (which is empty after
- // the first line) followed by the description line. This will make sure that multiline
- // descriptions are still right justified and aligned.
- for (StringPiece line : util::tokenize(flag.description, '\n')) {
- *out << " " << std::setw(kWidth) << std::left << argLine << line << "\n";
- argLine = " ";
- }
+ // Split the description by newlines and write out the argument (which is
+ // empty after
+ // the first line) followed by the description line. This will make sure
+ // that multiline
+ // descriptions are still right justified and aligned.
+ for (StringPiece line : util::tokenize(flag.description, '\n')) {
+ *out << " " << std::setw(kWidth) << std::left << argLine << line << "\n";
+ argLine = " ";
}
- *out << " " << std::setw(kWidth) << std::left << "-h" << "Displays this help menu\n";
- out->flush();
+ }
+ *out << " " << std::setw(kWidth) << std::left << "-h"
+ << "Displays this help menu\n";
+ out->flush();
}
-bool Flags::parse(const StringPiece& command, const std::vector<StringPiece>& args,
+bool Flags::parse(const StringPiece& command,
+ const std::vector<StringPiece>& args,
std::ostream* outError) {
- for (size_t i = 0; i < args.size(); i++) {
- StringPiece arg = args[i];
- if (*(arg.data()) != '-') {
- mArgs.push_back(arg.toString());
- continue;
- }
-
- if (arg == "-h" || arg == "--help") {
- usage(command, outError);
- return false;
- }
-
- bool match = false;
- for (Flag& flag : mFlags) {
- if (arg == flag.name) {
- if (flag.numArgs > 0) {
- i++;
- if (i >= args.size()) {
- *outError << flag.name << " missing argument.\n\n";
- usage(command, outError);
- return false;
- }
- flag.action(args[i]);
- } else {
- flag.action({});
- }
- flag.parsed = true;
- match = true;
- break;
- }
- }
-
- if (!match) {
- *outError << "unknown option '" << arg << "'.\n\n";
- usage(command, outError);
- return false;
- }
+ for (size_t i = 0; i < args.size(); i++) {
+ StringPiece arg = args[i];
+ if (*(arg.data()) != '-') {
+ mArgs.push_back(arg.toString());
+ continue;
}
- for (const Flag& flag : mFlags) {
- if (flag.required && !flag.parsed) {
- *outError << "missing required flag " << flag.name << "\n\n";
+ if (arg == "-h" || arg == "--help") {
+ usage(command, outError);
+ return false;
+ }
+
+ bool match = false;
+ for (Flag& flag : mFlags) {
+ if (arg == flag.name) {
+ if (flag.numArgs > 0) {
+ i++;
+ if (i >= args.size()) {
+ *outError << flag.name << " missing argument.\n\n";
usage(command, outError);
return false;
+ }
+ flag.action(args[i]);
+ } else {
+ flag.action({});
}
+ flag.parsed = true;
+ match = true;
+ break;
+ }
}
- return true;
+
+ if (!match) {
+ *outError << "unknown option '" << arg << "'.\n\n";
+ usage(command, outError);
+ return false;
+ }
+ }
+
+ for (const Flag& flag : mFlags) {
+ if (flag.required && !flag.parsed) {
+ *outError << "missing required flag " << flag.name << "\n\n";
+ usage(command, outError);
+ return false;
+ }
+ }
+ return true;
}
-const std::vector<std::string>& Flags::getArgs() {
- return mArgs;
-}
+const std::vector<std::string>& Flags::getArgs() { return mArgs; }
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Flags.h b/tools/aapt2/Flags.h
index b092855..4a0efdb 100644
--- a/tools/aapt2/Flags.h
+++ b/tools/aapt2/Flags.h
@@ -29,42 +29,45 @@
namespace aapt {
class Flags {
-public:
- Flags& requiredFlag(const StringPiece& name, const StringPiece& description,
- std::string* value);
- Flags& requiredFlagList(const StringPiece& name, const StringPiece& description,
- std::vector<std::string>* value);
- Flags& optionalFlag(const StringPiece& name, const StringPiece& description,
- Maybe<std::string>* value);
- Flags& optionalFlagList(const StringPiece& name, const StringPiece& description,
- std::vector<std::string>* value);
- Flags& optionalFlagList(const StringPiece& name, const StringPiece& description,
- std::unordered_set<std::string>* value);
- Flags& optionalSwitch(const StringPiece& name, const StringPiece& description,
- bool* value);
+ public:
+ Flags& requiredFlag(const StringPiece& name, const StringPiece& description,
+ std::string* value);
+ Flags& requiredFlagList(const StringPiece& name,
+ const StringPiece& description,
+ std::vector<std::string>* value);
+ Flags& optionalFlag(const StringPiece& name, const StringPiece& description,
+ Maybe<std::string>* value);
+ Flags& optionalFlagList(const StringPiece& name,
+ const StringPiece& description,
+ std::vector<std::string>* value);
+ Flags& optionalFlagList(const StringPiece& name,
+ const StringPiece& description,
+ std::unordered_set<std::string>* value);
+ Flags& optionalSwitch(const StringPiece& name, const StringPiece& description,
+ bool* value);
- void usage(const StringPiece& command, std::ostream* out);
+ void usage(const StringPiece& command, std::ostream* out);
- bool parse(const StringPiece& command, const std::vector<StringPiece>& args,
- std::ostream* outError);
+ bool parse(const StringPiece& command, const std::vector<StringPiece>& args,
+ std::ostream* outError);
- const std::vector<std::string>& getArgs();
+ const std::vector<std::string>& getArgs();
-private:
- struct Flag {
- std::string name;
- std::string description;
- std::function<bool(const StringPiece& value)> action;
- bool required;
- size_t numArgs;
+ private:
+ struct Flag {
+ std::string name;
+ std::string description;
+ std::function<bool(const StringPiece& value)> action;
+ bool required;
+ size_t numArgs;
- bool parsed;
- };
+ bool parsed;
+ };
- std::vector<Flag> mFlags;
- std::vector<std::string> mArgs;
+ std::vector<Flag> mFlags;
+ std::vector<std::string> mArgs;
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_FLAGS_H
+#endif // AAPT_FLAGS_H
diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp
index f7956c0..a0f7641 100644
--- a/tools/aapt2/Locale.cpp
+++ b/tools/aapt2/Locale.cpp
@@ -17,8 +17,8 @@
#include "Locale.h"
#include "util/Util.h"
-#include <algorithm>
#include <ctype.h>
+#include <algorithm>
#include <string>
#include <vector>
@@ -27,225 +27,226 @@
using android::ResTable_config;
void LocaleValue::setLanguage(const char* languageChars) {
- size_t i = 0;
- while ((*languageChars) != '\0') {
- language[i++] = ::tolower(*languageChars);
- languageChars++;
- }
+ size_t i = 0;
+ while ((*languageChars) != '\0') {
+ language[i++] = ::tolower(*languageChars);
+ languageChars++;
+ }
}
void LocaleValue::setRegion(const char* regionChars) {
- size_t i = 0;
- while ((*regionChars) != '\0') {
- region[i++] = ::toupper(*regionChars);
- regionChars++;
- }
+ size_t i = 0;
+ while ((*regionChars) != '\0') {
+ region[i++] = ::toupper(*regionChars);
+ regionChars++;
+ }
}
void LocaleValue::setScript(const char* scriptChars) {
- size_t i = 0;
- while ((*scriptChars) != '\0') {
- if (i == 0) {
- script[i++] = ::toupper(*scriptChars);
- } else {
- script[i++] = ::tolower(*scriptChars);
- }
- scriptChars++;
+ size_t i = 0;
+ while ((*scriptChars) != '\0') {
+ if (i == 0) {
+ script[i++] = ::toupper(*scriptChars);
+ } else {
+ script[i++] = ::tolower(*scriptChars);
}
+ scriptChars++;
+ }
}
void LocaleValue::setVariant(const char* variantChars) {
- size_t i = 0;
- while ((*variantChars) != '\0') {
- variant[i++] = *variantChars;
- variantChars++;
- }
+ size_t i = 0;
+ while ((*variantChars) != '\0') {
+ variant[i++] = *variantChars;
+ variantChars++;
+ }
}
static inline bool isAlpha(const std::string& str) {
- return std::all_of(std::begin(str), std::end(str), ::isalpha);
+ return std::all_of(std::begin(str), std::end(str), ::isalpha);
}
static inline bool isNumber(const std::string& str) {
- return std::all_of(std::begin(str), std::end(str), ::isdigit);
+ return std::all_of(std::begin(str), std::end(str), ::isdigit);
}
bool LocaleValue::initFromFilterString(const StringPiece& str) {
- // A locale (as specified in the filter) is an underscore separated name such
- // as "en_US", "en_Latn_US", or "en_US_POSIX".
- std::vector<std::string> parts = util::splitAndLowercase(str, '_');
+ // A locale (as specified in the filter) is an underscore separated name such
+ // as "en_US", "en_Latn_US", or "en_US_POSIX".
+ std::vector<std::string> parts = util::splitAndLowercase(str, '_');
- const int numTags = parts.size();
- bool valid = false;
- if (numTags >= 1) {
- const std::string& lang = parts[0];
- if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
- setLanguage(lang.c_str());
- valid = true;
- }
- }
+ const int numTags = parts.size();
+ bool valid = false;
+ if (numTags >= 1) {
+ const std::string& lang = parts[0];
+ if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
+ setLanguage(lang.c_str());
+ valid = true;
+ }
+ }
- if (!valid || numTags == 1) {
- return valid;
- }
+ if (!valid || numTags == 1) {
+ return valid;
+ }
- // At this point, valid == true && numTags > 1.
- const std::string& part2 = parts[1];
- if ((part2.length() == 2 && isAlpha(part2)) ||
- (part2.length() == 3 && isNumber(part2))) {
- setRegion(part2.c_str());
- } else if (part2.length() == 4 && isAlpha(part2)) {
- setScript(part2.c_str());
- } else if (part2.length() >= 4 && part2.length() <= 8) {
- setVariant(part2.c_str());
- } else {
- valid = false;
- }
+ // At this point, valid == true && numTags > 1.
+ const std::string& part2 = parts[1];
+ if ((part2.length() == 2 && isAlpha(part2)) ||
+ (part2.length() == 3 && isNumber(part2))) {
+ setRegion(part2.c_str());
+ } else if (part2.length() == 4 && isAlpha(part2)) {
+ setScript(part2.c_str());
+ } else if (part2.length() >= 4 && part2.length() <= 8) {
+ setVariant(part2.c_str());
+ } else {
+ valid = false;
+ }
- if (!valid || numTags == 2) {
- return valid;
- }
+ if (!valid || numTags == 2) {
+ return valid;
+ }
- // At this point, valid == true && numTags > 1.
- const std::string& part3 = parts[2];
- if (((part3.length() == 2 && isAlpha(part3)) ||
- (part3.length() == 3 && isNumber(part3))) && script[0]) {
- setRegion(part3.c_str());
- } else if (part3.length() >= 4 && part3.length() <= 8) {
- setVariant(part3.c_str());
- } else {
- valid = false;
- }
+ // At this point, valid == true && numTags > 1.
+ const std::string& part3 = parts[2];
+ if (((part3.length() == 2 && isAlpha(part3)) ||
+ (part3.length() == 3 && isNumber(part3))) &&
+ script[0]) {
+ setRegion(part3.c_str());
+ } else if (part3.length() >= 4 && part3.length() <= 8) {
+ setVariant(part3.c_str());
+ } else {
+ valid = false;
+ }
- if (!valid || numTags == 3) {
- return valid;
- }
+ if (!valid || numTags == 3) {
+ return valid;
+ }
- const std::string& part4 = parts[3];
- if (part4.length() >= 4 && part4.length() <= 8) {
- setVariant(part4.c_str());
- } else {
- valid = false;
- }
+ const std::string& part4 = parts[3];
+ if (part4.length() >= 4 && part4.length() <= 8) {
+ setVariant(part4.c_str());
+ } else {
+ valid = false;
+ }
- if (!valid || numTags > 4) {
- return false;
- }
+ if (!valid || numTags > 4) {
+ return false;
+ }
- return true;
+ return true;
}
ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter,
- std::vector<std::string>::iterator end) {
- const std::vector<std::string>::iterator startIter = iter;
+ std::vector<std::string>::iterator end) {
+ const std::vector<std::string>::iterator startIter = iter;
- std::string& part = *iter;
- if (part[0] == 'b' && part[1] == '+') {
- // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
- // except that the separator is "+" and not "-".
- std::vector<std::string> subtags = util::splitAndLowercase(part, '+');
- subtags.erase(subtags.begin());
- if (subtags.size() == 1) {
- setLanguage(subtags[0].c_str());
- } else if (subtags.size() == 2) {
- setLanguage(subtags[0].c_str());
+ std::string& part = *iter;
+ if (part[0] == 'b' && part[1] == '+') {
+ // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
+ // except that the separator is "+" and not "-".
+ std::vector<std::string> subtags = util::splitAndLowercase(part, '+');
+ subtags.erase(subtags.begin());
+ if (subtags.size() == 1) {
+ setLanguage(subtags[0].c_str());
+ } else if (subtags.size() == 2) {
+ setLanguage(subtags[0].c_str());
- // The second tag can either be a region, a variant or a script.
- switch (subtags[1].size()) {
- case 2:
- case 3:
- setRegion(subtags[1].c_str());
- break;
- case 4:
- if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
- // This is a variant: fall through
- } else {
- setScript(subtags[1].c_str());
- break;
- }
- case 5:
- case 6:
- case 7:
- case 8:
- setVariant(subtags[1].c_str());
- break;
- default:
- return -1;
- }
- } else if (subtags.size() == 3) {
- // The language is always the first subtag.
- setLanguage(subtags[0].c_str());
-
- // The second subtag can either be a script or a region code.
- // If its size is 4, it's a script code, else it's a region code.
- if (subtags[1].size() == 4) {
- setScript(subtags[1].c_str());
- } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
- setRegion(subtags[1].c_str());
- } else {
- return -1;
- }
-
- // The third tag can either be a region code (if the second tag was
- // a script), else a variant code.
- if (subtags[2].size() >= 4) {
- setVariant(subtags[2].c_str());
- } else {
- setRegion(subtags[2].c_str());
- }
- } else if (subtags.size() == 4) {
- setLanguage(subtags[0].c_str());
+ // The second tag can either be a region, a variant or a script.
+ switch (subtags[1].size()) {
+ case 2:
+ case 3:
+ setRegion(subtags[1].c_str());
+ break;
+ case 4:
+ if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
+ // This is a variant: fall through
+ } else {
setScript(subtags[1].c_str());
- setRegion(subtags[2].c_str());
- setVariant(subtags[3].c_str());
- } else {
- return -1;
- }
+ break;
+ }
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ setVariant(subtags[1].c_str());
+ break;
+ default:
+ return -1;
+ }
+ } else if (subtags.size() == 3) {
+ // The language is always the first subtag.
+ setLanguage(subtags[0].c_str());
- ++iter;
+ // The second subtag can either be a script or a region code.
+ // If its size is 4, it's a script code, else it's a region code.
+ if (subtags[1].size() == 4) {
+ setScript(subtags[1].c_str());
+ } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
+ setRegion(subtags[1].c_str());
+ } else {
+ return -1;
+ }
+ // The third tag can either be a region code (if the second tag was
+ // a script), else a variant code.
+ if (subtags[2].size() >= 4) {
+ setVariant(subtags[2].c_str());
+ } else {
+ setRegion(subtags[2].c_str());
+ }
+ } else if (subtags.size() == 4) {
+ setLanguage(subtags[0].c_str());
+ setScript(subtags[1].c_str());
+ setRegion(subtags[2].c_str());
+ setVariant(subtags[3].c_str());
} else {
- if ((part.length() == 2 || part.length() == 3)
- && isAlpha(part) && part != "car") {
- setLanguage(part.c_str());
- ++iter;
-
- if (iter != end) {
- const std::string& regionPart = *iter;
- if (regionPart.c_str()[0] == 'r' && regionPart.length() == 3) {
- setRegion(regionPart.c_str() + 1);
- ++iter;
- }
- }
- }
+ return -1;
}
- return static_cast<ssize_t>(iter - startIter);
+ ++iter;
+
+ } else {
+ if ((part.length() == 2 || part.length() == 3) && isAlpha(part) &&
+ part != "car") {
+ setLanguage(part.c_str());
+ ++iter;
+
+ if (iter != end) {
+ const std::string& regionPart = *iter;
+ if (regionPart.c_str()[0] == 'r' && regionPart.length() == 3) {
+ setRegion(regionPart.c_str() + 1);
+ ++iter;
+ }
+ }
+ }
+ }
+
+ return static_cast<ssize_t>(iter - startIter);
}
void LocaleValue::initFromResTable(const ResTable_config& config) {
- config.unpackLanguage(language);
- config.unpackRegion(region);
- if (config.localeScript[0] && !config.localeScriptWasComputed) {
- memcpy(script, config.localeScript, sizeof(config.localeScript));
- }
+ config.unpackLanguage(language);
+ config.unpackRegion(region);
+ if (config.localeScript[0] && !config.localeScriptWasComputed) {
+ memcpy(script, config.localeScript, sizeof(config.localeScript));
+ }
- if (config.localeVariant[0]) {
- memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
- }
+ if (config.localeVariant[0]) {
+ memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
+ }
}
void LocaleValue::writeTo(ResTable_config* out) const {
- out->packLanguage(language);
- out->packRegion(region);
+ out->packLanguage(language);
+ out->packRegion(region);
- if (script[0]) {
- memcpy(out->localeScript, script, sizeof(out->localeScript));
- }
+ if (script[0]) {
+ memcpy(out->localeScript, script, sizeof(out->localeScript));
+ }
- if (variant[0]) {
- memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
- }
+ if (variant[0]) {
+ memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
+ }
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Locale.h b/tools/aapt2/Locale.h
index 33f80ad..0f75850 100644
--- a/tools/aapt2/Locale.h
+++ b/tools/aapt2/Locale.h
@@ -29,86 +29,84 @@
* A convenience class to build and parse locales.
*/
struct LocaleValue {
- char language[4];
- char region[4];
- char script[4];
- char variant[8];
+ char language[4];
+ char region[4];
+ char script[4];
+ char variant[8];
- inline LocaleValue();
+ inline LocaleValue();
- /**
- * Initialize this LocaleValue from a config string.
- */
- bool initFromFilterString(const StringPiece& config);
+ /**
+ * Initialize this LocaleValue from a config string.
+ */
+ bool initFromFilterString(const StringPiece& config);
- /**
- * Initialize this LocaleValue from parts of a vector.
- */
- ssize_t initFromParts(std::vector<std::string>::iterator iter,
- std::vector<std::string>::iterator end);
+ /**
+ * Initialize this LocaleValue from parts of a vector.
+ */
+ ssize_t initFromParts(std::vector<std::string>::iterator iter,
+ std::vector<std::string>::iterator end);
- /**
- * Initialize this LocaleValue from a ResTable_config.
- */
- void initFromResTable(const android::ResTable_config& config);
+ /**
+ * Initialize this LocaleValue from a ResTable_config.
+ */
+ void initFromResTable(const android::ResTable_config& config);
- /**
- * Set the locale in a ResTable_config from this LocaleValue.
- */
- void writeTo(android::ResTable_config* out) const;
+ /**
+ * Set the locale in a ResTable_config from this LocaleValue.
+ */
+ void writeTo(android::ResTable_config* out) const;
- inline int compare(const LocaleValue& other) const;
+ inline int compare(const LocaleValue& other) const;
- inline bool operator<(const LocaleValue& o) const;
- inline bool operator<=(const LocaleValue& o) const;
- inline bool operator==(const LocaleValue& o) const;
- inline bool operator!=(const LocaleValue& o) const;
- inline bool operator>=(const LocaleValue& o) const;
- inline bool operator>(const LocaleValue& o) const;
+ inline bool operator<(const LocaleValue& o) const;
+ inline bool operator<=(const LocaleValue& o) const;
+ inline bool operator==(const LocaleValue& o) const;
+ inline bool operator!=(const LocaleValue& o) const;
+ inline bool operator>=(const LocaleValue& o) const;
+ inline bool operator>(const LocaleValue& o) const;
-private:
- void setLanguage(const char* language);
- void setRegion(const char* language);
- void setScript(const char* script);
- void setVariant(const char* variant);
+ private:
+ void setLanguage(const char* language);
+ void setRegion(const char* language);
+ void setScript(const char* script);
+ void setVariant(const char* variant);
};
//
// Implementation
//
-LocaleValue::LocaleValue() {
- memset(this, 0, sizeof(LocaleValue));
-}
+LocaleValue::LocaleValue() { memset(this, 0, sizeof(LocaleValue)); }
int LocaleValue::compare(const LocaleValue& other) const {
- return memcmp(this, &other, sizeof(LocaleValue));
+ return memcmp(this, &other, sizeof(LocaleValue));
}
bool LocaleValue::operator<(const LocaleValue& o) const {
- return compare(o) < 0;
+ return compare(o) < 0;
}
bool LocaleValue::operator<=(const LocaleValue& o) const {
- return compare(o) <= 0;
+ return compare(o) <= 0;
}
bool LocaleValue::operator==(const LocaleValue& o) const {
- return compare(o) == 0;
+ return compare(o) == 0;
}
bool LocaleValue::operator!=(const LocaleValue& o) const {
- return compare(o) != 0;
+ return compare(o) != 0;
}
bool LocaleValue::operator>=(const LocaleValue& o) const {
- return compare(o) >= 0;
+ return compare(o) >= 0;
}
bool LocaleValue::operator>(const LocaleValue& o) const {
- return compare(o) > 0;
+ return compare(o) > 0;
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_LOCALE_VALUE_H
+#endif // AAPT_LOCALE_VALUE_H
diff --git a/tools/aapt2/Locale_test.cpp b/tools/aapt2/Locale_test.cpp
index e4b8ce7..b82d2b9 100644
--- a/tools/aapt2/Locale_test.cpp
+++ b/tools/aapt2/Locale_test.cpp
@@ -22,61 +22,73 @@
namespace aapt {
-static ::testing::AssertionResult TestLanguage(const char* input, const char* lang) {
- std::vector<std::string> parts = util::splitAndLowercase(input, '-');
- LocaleValue lv;
- ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
- if (count < 0) {
- return ::testing::AssertionFailure() << " failed to parse '" << input << "'.";
- }
+static ::testing::AssertionResult TestLanguage(const char* input,
+ const char* lang) {
+ std::vector<std::string> parts = util::splitAndLowercase(input, '-');
+ LocaleValue lv;
+ ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
+ if (count < 0) {
+ return ::testing::AssertionFailure() << " failed to parse '" << input
+ << "'.";
+ }
- if (count != 1) {
- return ::testing::AssertionFailure() << count
- << " parts were consumed parsing '" << input << "' but expected 1.";
- }
+ if (count != 1) {
+ return ::testing::AssertionFailure()
+ << count << " parts were consumed parsing '" << input
+ << "' but expected 1.";
+ }
- if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) != 0) {
- return ::testing::AssertionFailure() << "expected " << lang << " but got "
- << std::string(lv.language, sizeof(lv.language)) << ".";
- }
+ if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) !=
+ 0) {
+ return ::testing::AssertionFailure()
+ << "expected " << lang << " but got "
+ << std::string(lv.language, sizeof(lv.language)) << ".";
+ }
- return ::testing::AssertionSuccess();
+ return ::testing::AssertionSuccess();
}
-static ::testing::AssertionResult TestLanguageRegion(const char* input, const char* lang,
+static ::testing::AssertionResult TestLanguageRegion(const char* input,
+ const char* lang,
const char* region) {
- std::vector<std::string> parts = util::splitAndLowercase(input, '-');
- LocaleValue lv;
- ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
- if (count < 0) {
- return ::testing::AssertionFailure() << " failed to parse '" << input << "'.";
- }
+ std::vector<std::string> parts = util::splitAndLowercase(input, '-');
+ LocaleValue lv;
+ ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
+ if (count < 0) {
+ return ::testing::AssertionFailure() << " failed to parse '" << input
+ << "'.";
+ }
- if (count != 2) {
- return ::testing::AssertionFailure() << count
- << " parts were consumed parsing '" << input << "' but expected 2.";
- }
+ if (count != 2) {
+ return ::testing::AssertionFailure()
+ << count << " parts were consumed parsing '" << input
+ << "' but expected 2.";
+ }
- if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) != 0) {
- return ::testing::AssertionFailure() << "expected " << input << " but got "
- << std::string(lv.language, sizeof(lv.language)) << ".";
- }
+ if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) !=
+ 0) {
+ return ::testing::AssertionFailure()
+ << "expected " << input << " but got "
+ << std::string(lv.language, sizeof(lv.language)) << ".";
+ }
- if (memcmp(lv.region, region, std::min(strlen(region), sizeof(lv.region))) != 0) {
- return ::testing::AssertionFailure() << "expected " << region << " but got "
- << std::string(lv.region, sizeof(lv.region)) << ".";
- }
+ if (memcmp(lv.region, region, std::min(strlen(region), sizeof(lv.region))) !=
+ 0) {
+ return ::testing::AssertionFailure()
+ << "expected " << region << " but got "
+ << std::string(lv.region, sizeof(lv.region)) << ".";
+ }
- return ::testing::AssertionSuccess();
+ return ::testing::AssertionSuccess();
}
TEST(ConfigDescriptionTest, ParseLanguage) {
- EXPECT_TRUE(TestLanguage("en", "en"));
- EXPECT_TRUE(TestLanguage("fr", "fr"));
- EXPECT_FALSE(TestLanguage("land", ""));
- EXPECT_TRUE(TestLanguage("fr-land", "fr"));
+ EXPECT_TRUE(TestLanguage("en", "en"));
+ EXPECT_TRUE(TestLanguage("fr", "fr"));
+ EXPECT_FALSE(TestLanguage("land", ""));
+ EXPECT_TRUE(TestLanguage("fr-land", "fr"));
- EXPECT_TRUE(TestLanguageRegion("fr-rCA", "fr", "CA"));
+ EXPECT_TRUE(TestLanguageRegion("fr-rCA", "fr", "CA"));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index f3f70d6..8aedd44 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -28,9 +28,9 @@
static const char* sMinorVersion = "2";
int printVersion() {
- std::cerr << "Android Asset Packaging Tool (aapt) "
- << sMajorVersion << "." << sMinorVersion << std::endl;
- return 0;
+ std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."
+ << sMinorVersion << std::endl;
+ return 0;
}
extern int compile(const std::vector<StringPiece>& args);
@@ -38,35 +38,36 @@
extern int dump(const std::vector<StringPiece>& args);
extern int diff(const std::vector<StringPiece>& args);
-} // namespace aapt
+} // namespace aapt
int main(int argc, char** argv) {
- if (argc >= 2) {
- argv += 1;
- argc -= 1;
+ if (argc >= 2) {
+ argv += 1;
+ argc -= 1;
- std::vector<aapt::StringPiece> args;
- for (int i = 1; i < argc; i++) {
- args.push_back(argv[i]);
- }
-
- aapt::StringPiece command(argv[0]);
- if (command == "compile" || command == "c") {
- return aapt::compile(args);
- } else if (command == "link" || command == "l") {
- return aapt::link(args);
- } else if (command == "dump" || command == "d") {
- return aapt::dump(args);
- } else if (command == "diff") {
- return aapt::diff(args);
- } else if (command == "version") {
- return aapt::printVersion();
- }
- std::cerr << "unknown command '" << command << "'\n";
- } else {
- std::cerr << "no command specified\n";
+ std::vector<aapt::StringPiece> args;
+ for (int i = 1; i < argc; i++) {
+ args.push_back(argv[i]);
}
- std::cerr << "\nusage: aapt2 [compile|link|dump|diff|version] ..." << std::endl;
- return 1;
+ aapt::StringPiece command(argv[0]);
+ if (command == "compile" || command == "c") {
+ return aapt::compile(args);
+ } else if (command == "link" || command == "l") {
+ return aapt::link(args);
+ } else if (command == "dump" || command == "d") {
+ return aapt::dump(args);
+ } else if (command == "diff") {
+ return aapt::diff(args);
+ } else if (command == "version") {
+ return aapt::printVersion();
+ }
+ std::cerr << "unknown command '" << command << "'\n";
+ } else {
+ std::cerr << "no command specified\n";
+ }
+
+ std::cerr << "\nusage: aapt2 [compile|link|dump|diff|version] ..."
+ << std::endl;
+ return 1;
}
diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h
index b6aaa4d..6d244aa 100644
--- a/tools/aapt2/NameMangler.h
+++ b/tools/aapt2/NameMangler.h
@@ -26,69 +26,71 @@
namespace aapt {
struct NameManglerPolicy {
- /**
- * Represents the package we are trying to build. References pointing
- * to this package are not mangled, and mangled references inherit this package name.
- */
- std::string targetPackageName;
+ /**
+ * Represents the package we are trying to build. References pointing
+ * to this package are not mangled, and mangled references inherit this
+ * package name.
+ */
+ std::string targetPackageName;
- /**
- * We must know which references to mangle, and which to keep (android vs. com.android.support).
- */
- std::set<std::string> packagesToMangle;
+ /**
+ * We must know which references to mangle, and which to keep (android vs.
+ * com.android.support).
+ */
+ std::set<std::string> packagesToMangle;
};
class NameMangler {
-private:
- NameManglerPolicy mPolicy;
+ private:
+ NameManglerPolicy mPolicy;
-public:
- explicit NameMangler(NameManglerPolicy policy) : mPolicy(policy) {
+ public:
+ explicit NameMangler(NameManglerPolicy policy) : mPolicy(policy) {}
+
+ Maybe<ResourceName> mangleName(const ResourceName& name) {
+ if (mPolicy.targetPackageName == name.package ||
+ mPolicy.packagesToMangle.count(name.package) == 0) {
+ return {};
}
- Maybe<ResourceName> mangleName(const ResourceName& name) {
- if (mPolicy.targetPackageName == name.package ||
- mPolicy.packagesToMangle.count(name.package) == 0) {
- return {};
- }
+ std::string mangledEntryName = mangleEntry(name.package, name.entry);
+ return ResourceName(mPolicy.targetPackageName, name.type, mangledEntryName);
+ }
- std::string mangledEntryName = mangleEntry(name.package, name.entry);
- return ResourceName(mPolicy.targetPackageName, name.type, mangledEntryName);
+ bool shouldMangle(const std::string& package) const {
+ if (package.empty() || mPolicy.targetPackageName == package) {
+ return false;
+ }
+ return mPolicy.packagesToMangle.count(package) != 0;
+ }
+
+ /**
+ * Returns a mangled name that is a combination of `name` and `package`.
+ * The mangled name should contain symbols that are illegal to define in XML,
+ * so that there will never be name mangling collisions.
+ */
+ static std::string mangleEntry(const std::string& package,
+ const std::string& name) {
+ return package + "$" + name;
+ }
+
+ /**
+ * Unmangles the name in `outName`, storing the correct name back in `outName`
+ * and the package in `outPackage`. Returns true if the name was unmangled or
+ * false if the name was never mangled to begin with.
+ */
+ static bool unmangle(std::string* outName, std::string* outPackage) {
+ size_t pivot = outName->find('$');
+ if (pivot == std::string::npos) {
+ return false;
}
- bool shouldMangle(const std::string& package) const {
- if (package.empty() || mPolicy.targetPackageName == package) {
- return false;
- }
- return mPolicy.packagesToMangle.count(package) != 0;
- }
-
- /**
- * Returns a mangled name that is a combination of `name` and `package`.
- * The mangled name should contain symbols that are illegal to define in XML,
- * so that there will never be name mangling collisions.
- */
- static std::string mangleEntry(const std::string& package, const std::string& name) {
- return package + "$" + name;
- }
-
- /**
- * Unmangles the name in `outName`, storing the correct name back in `outName`
- * and the package in `outPackage`. Returns true if the name was unmangled or
- * false if the name was never mangled to begin with.
- */
- static bool unmangle(std::string* outName, std::string* outPackage) {
- size_t pivot = outName->find('$');
- if (pivot == std::string::npos) {
- return false;
- }
-
- outPackage->assign(outName->data(), pivot);
- outName->assign(outName->data() + pivot + 1, outName->size() - (pivot + 1));
- return true;
- }
+ outPackage->assign(outName->data(), pivot);
+ outName->assign(outName->data() + pivot + 1, outName->size() - (pivot + 1));
+ return true;
+ }
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_NAME_MANGLER_H
+#endif // AAPT_NAME_MANGLER_H
diff --git a/tools/aapt2/NameMangler_test.cpp b/tools/aapt2/NameMangler_test.cpp
index f624df2..b02986d 100644
--- a/tools/aapt2/NameMangler_test.cpp
+++ b/tools/aapt2/NameMangler_test.cpp
@@ -22,25 +22,25 @@
namespace aapt {
TEST(NameManglerTest, MangleName) {
- std::string package = "android.appcompat";
- std::string name = "Platform.AppCompat";
+ std::string package = "android.appcompat";
+ std::string name = "Platform.AppCompat";
- std::string mangledName = NameMangler::mangleEntry(package, name);
- EXPECT_EQ(mangledName, "android.appcompat$Platform.AppCompat");
+ std::string mangledName = NameMangler::mangleEntry(package, name);
+ EXPECT_EQ(mangledName, "android.appcompat$Platform.AppCompat");
- std::string unmangledPackage;
- std::string unmangledName = mangledName;
- ASSERT_TRUE(NameMangler::unmangle(&unmangledName, &unmangledPackage));
- EXPECT_EQ(unmangledName, "Platform.AppCompat");
- EXPECT_EQ(unmangledPackage, "android.appcompat");
+ std::string unmangledPackage;
+ std::string unmangledName = mangledName;
+ ASSERT_TRUE(NameMangler::unmangle(&unmangledName, &unmangledPackage));
+ EXPECT_EQ(unmangledName, "Platform.AppCompat");
+ EXPECT_EQ(unmangledPackage, "android.appcompat");
}
TEST(NameManglerTest, IgnoreUnmangledName) {
- std::string package;
- std::string name = "foo_bar";
+ std::string package;
+ std::string name = "foo_bar";
- EXPECT_FALSE(NameMangler::unmangle(&name, &package));
- EXPECT_EQ(name, "foo_bar");
+ EXPECT_FALSE(NameMangler::unmangle(&name, &package));
+ EXPECT_EQ(name, "foo_bar");
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
index b7a091e..6805631 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -23,74 +23,97 @@
namespace aapt {
StringPiece toString(ResourceType type) {
- switch (type) {
- case ResourceType::kAnim: return "anim";
- case ResourceType::kAnimator: return "animator";
- case ResourceType::kArray: return "array";
- case ResourceType::kAttr: return "attr";
- case ResourceType::kAttrPrivate: return "^attr-private";
- case ResourceType::kBool: return "bool";
- case ResourceType::kColor: return "color";
- case ResourceType::kDimen: return "dimen";
- case ResourceType::kDrawable: return "drawable";
- case ResourceType::kFraction: return "fraction";
- case ResourceType::kId: return "id";
- case ResourceType::kInteger: return "integer";
- case ResourceType::kInterpolator: return "interpolator";
- case ResourceType::kLayout: return "layout";
- case ResourceType::kMenu: return "menu";
- case ResourceType::kMipmap: return "mipmap";
- case ResourceType::kPlurals: return "plurals";
- case ResourceType::kRaw: return "raw";
- case ResourceType::kString: return "string";
- case ResourceType::kStyle: return "style";
- case ResourceType::kStyleable: return "styleable";
- case ResourceType::kTransition: return "transition";
- case ResourceType::kXml: return "xml";
- }
- return {};
+ switch (type) {
+ case ResourceType::kAnim:
+ return "anim";
+ case ResourceType::kAnimator:
+ return "animator";
+ case ResourceType::kArray:
+ return "array";
+ case ResourceType::kAttr:
+ return "attr";
+ case ResourceType::kAttrPrivate:
+ return "^attr-private";
+ case ResourceType::kBool:
+ return "bool";
+ case ResourceType::kColor:
+ return "color";
+ case ResourceType::kDimen:
+ return "dimen";
+ case ResourceType::kDrawable:
+ return "drawable";
+ case ResourceType::kFraction:
+ return "fraction";
+ case ResourceType::kId:
+ return "id";
+ case ResourceType::kInteger:
+ return "integer";
+ case ResourceType::kInterpolator:
+ return "interpolator";
+ case ResourceType::kLayout:
+ return "layout";
+ case ResourceType::kMenu:
+ return "menu";
+ case ResourceType::kMipmap:
+ return "mipmap";
+ case ResourceType::kPlurals:
+ return "plurals";
+ case ResourceType::kRaw:
+ return "raw";
+ case ResourceType::kString:
+ return "string";
+ case ResourceType::kStyle:
+ return "style";
+ case ResourceType::kStyleable:
+ return "styleable";
+ case ResourceType::kTransition:
+ return "transition";
+ case ResourceType::kXml:
+ return "xml";
+ }
+ return {};
}
-static const std::map<StringPiece, ResourceType> sResourceTypeMap {
- { "anim", ResourceType::kAnim },
- { "animator", ResourceType::kAnimator },
- { "array", ResourceType::kArray },
- { "attr", ResourceType::kAttr },
- { "^attr-private", ResourceType::kAttrPrivate },
- { "bool", ResourceType::kBool },
- { "color", ResourceType::kColor },
- { "dimen", ResourceType::kDimen },
- { "drawable", ResourceType::kDrawable },
- { "fraction", ResourceType::kFraction },
- { "id", ResourceType::kId },
- { "integer", ResourceType::kInteger },
- { "interpolator", ResourceType::kInterpolator },
- { "layout", ResourceType::kLayout },
- { "menu", ResourceType::kMenu },
- { "mipmap", ResourceType::kMipmap },
- { "plurals", ResourceType::kPlurals },
- { "raw", ResourceType::kRaw },
- { "string", ResourceType::kString },
- { "style", ResourceType::kStyle },
- { "styleable", ResourceType::kStyleable },
- { "transition", ResourceType::kTransition },
- { "xml", ResourceType::kXml },
+static const std::map<StringPiece, ResourceType> sResourceTypeMap{
+ {"anim", ResourceType::kAnim},
+ {"animator", ResourceType::kAnimator},
+ {"array", ResourceType::kArray},
+ {"attr", ResourceType::kAttr},
+ {"^attr-private", ResourceType::kAttrPrivate},
+ {"bool", ResourceType::kBool},
+ {"color", ResourceType::kColor},
+ {"dimen", ResourceType::kDimen},
+ {"drawable", ResourceType::kDrawable},
+ {"fraction", ResourceType::kFraction},
+ {"id", ResourceType::kId},
+ {"integer", ResourceType::kInteger},
+ {"interpolator", ResourceType::kInterpolator},
+ {"layout", ResourceType::kLayout},
+ {"menu", ResourceType::kMenu},
+ {"mipmap", ResourceType::kMipmap},
+ {"plurals", ResourceType::kPlurals},
+ {"raw", ResourceType::kRaw},
+ {"string", ResourceType::kString},
+ {"style", ResourceType::kStyle},
+ {"styleable", ResourceType::kStyleable},
+ {"transition", ResourceType::kTransition},
+ {"xml", ResourceType::kXml},
};
const ResourceType* parseResourceType(const StringPiece& str) {
- auto iter = sResourceTypeMap.find(str);
- if (iter == std::end(sResourceTypeMap)) {
- return nullptr;
- }
- return &iter->second;
+ auto iter = sResourceTypeMap.find(str);
+ if (iter == std::end(sResourceTypeMap)) {
+ return nullptr;
+ }
+ return &iter->second;
}
bool operator<(const ResourceKey& a, const ResourceKey& b) {
- return std::tie(a.name, a.config) < std::tie(b.name, b.config);
+ return std::tie(a.name, a.config) < std::tie(b.name, b.config);
}
bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b) {
- return std::tie(a.name, a.config) < std::tie(b.name, b.config);
+ return std::tie(a.name, a.config) < std::tie(b.name, b.config);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 2969b8c..30739db 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -37,29 +37,29 @@
* to the 'type' in package:type/entry.
*/
enum class ResourceType {
- kAnim,
- kAnimator,
- kArray,
- kAttr,
- kAttrPrivate,
- kBool,
- kColor,
- kDimen,
- kDrawable,
- kFraction,
- kId,
- kInteger,
- kInterpolator,
- kLayout,
- kMenu,
- kMipmap,
- kPlurals,
- kRaw,
- kString,
- kStyle,
- kStyleable,
- kTransition,
- kXml,
+ kAnim,
+ kAnimator,
+ kArray,
+ kAttr,
+ kAttrPrivate,
+ kBool,
+ kColor,
+ kDimen,
+ kDrawable,
+ kFraction,
+ kId,
+ kInteger,
+ kInterpolator,
+ kLayout,
+ kMenu,
+ kMipmap,
+ kPlurals,
+ kRaw,
+ kString,
+ kStyle,
+ kStyleable,
+ kTransition,
+ kXml,
};
StringPiece toString(ResourceType type);
@@ -75,17 +75,17 @@
* a resource in the ResourceTable.
*/
struct ResourceName {
- std::string package;
- ResourceType type;
- std::string entry;
+ std::string package;
+ ResourceType type;
+ std::string entry;
- ResourceName() : type(ResourceType::kRaw) {}
- ResourceName(const StringPiece& p, ResourceType t, const StringPiece& e);
+ ResourceName() : type(ResourceType::kRaw) {}
+ ResourceName(const StringPiece& p, ResourceType t, const StringPiece& e);
- int compare(const ResourceName& other) const;
+ int compare(const ResourceName& other) const;
- bool isValid() const;
- std::string toString() const;
+ bool isValid() const;
+ std::string toString() const;
};
/**
@@ -95,21 +95,21 @@
* of the original string.
*/
struct ResourceNameRef {
- StringPiece package;
- ResourceType type;
- StringPiece entry;
+ StringPiece package;
+ ResourceType type;
+ StringPiece entry;
- ResourceNameRef() = default;
- ResourceNameRef(const ResourceNameRef&) = default;
- ResourceNameRef(ResourceNameRef&&) = default;
- ResourceNameRef(const ResourceName& rhs); // NOLINT(implicit)
- ResourceNameRef(const StringPiece& p, ResourceType t, const StringPiece& e);
- ResourceNameRef& operator=(const ResourceNameRef& rhs) = default;
- ResourceNameRef& operator=(ResourceNameRef&& rhs) = default;
- ResourceNameRef& operator=(const ResourceName& rhs);
+ ResourceNameRef() = default;
+ ResourceNameRef(const ResourceNameRef&) = default;
+ ResourceNameRef(ResourceNameRef&&) = default;
+ ResourceNameRef(const ResourceName& rhs); // NOLINT(implicit)
+ ResourceNameRef(const StringPiece& p, ResourceType t, const StringPiece& e);
+ ResourceNameRef& operator=(const ResourceNameRef& rhs) = default;
+ ResourceNameRef& operator=(ResourceNameRef&& rhs) = default;
+ ResourceNameRef& operator=(const ResourceName& rhs);
- ResourceName toResourceName() const;
- bool isValid() const;
+ ResourceName toResourceName() const;
+ bool isValid() const;
};
/**
@@ -124,64 +124,66 @@
* EEEE: 16 bit entry identifier.
*/
struct ResourceId {
- uint32_t id;
+ uint32_t id;
- ResourceId();
- ResourceId(const ResourceId& rhs);
- ResourceId(uint32_t resId); // NOLINT(implicit)
- ResourceId(uint8_t p, uint8_t t, uint16_t e);
+ ResourceId();
+ ResourceId(const ResourceId& rhs);
+ ResourceId(uint32_t resId); // NOLINT(implicit)
+ ResourceId(uint8_t p, uint8_t t, uint16_t e);
- bool isValid() const;
- uint8_t packageId() const;
- uint8_t typeId() const;
- uint16_t entryId() const;
+ bool isValid() const;
+ uint8_t packageId() const;
+ uint8_t typeId() const;
+ uint16_t entryId() const;
};
struct SourcedResourceName {
- ResourceName name;
- size_t line;
+ ResourceName name;
+ size_t line;
};
struct ResourceFile {
- // Name
- ResourceName name;
+ // Name
+ ResourceName name;
- // Configuration
- ConfigDescription config;
+ // Configuration
+ ConfigDescription config;
- // Source
- Source source;
+ // Source
+ Source source;
- // Exported symbols
- std::vector<SourcedResourceName> exportedSymbols;
+ // Exported symbols
+ std::vector<SourcedResourceName> exportedSymbols;
};
/**
- * Useful struct used as a key to represent a unique resource in associative containers.
+ * Useful struct used as a key to represent a unique resource in associative
+ * containers.
*/
struct ResourceKey {
- ResourceName name;
- ConfigDescription config;
+ ResourceName name;
+ ConfigDescription config;
};
bool operator<(const ResourceKey& a, const ResourceKey& b);
/**
- * Useful struct used as a key to represent a unique resource in associative containers.
+ * Useful struct used as a key to represent a unique resource in associative
+ * containers.
* Holds a reference to the name, so that name better live longer than this key!
*/
struct ResourceKeyRef {
- ResourceNameRef name;
- ConfigDescription config;
+ ResourceNameRef name;
+ ConfigDescription config;
- ResourceKeyRef() = default;
- ResourceKeyRef(const ResourceNameRef& n, const ConfigDescription& c) : name(n), config(c) {
- }
+ ResourceKeyRef() = default;
+ ResourceKeyRef(const ResourceNameRef& n, const ConfigDescription& c)
+ : name(n), config(c) {}
- /**
- * Prevent taking a reference to a temporary. This is bad.
- */
- ResourceKeyRef(ResourceName&& n, const ConfigDescription& c) = delete;
+ /**
+ * Prevent taking a reference to a temporary. This is bad.
+ */
+ ResourceKeyRef(ResourceName&& n, const ConfigDescription& c) = delete;
};
bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b);
@@ -190,193 +192,194 @@
// ResourceId implementation.
//
-inline ResourceId::ResourceId() : id(0) {
-}
+inline ResourceId::ResourceId() : id(0) {}
-inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {
-}
+inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {}
-inline ResourceId::ResourceId(uint32_t resId) : id(resId) {
-}
+inline ResourceId::ResourceId(uint32_t resId) : id(resId) {}
-inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e) : id((p << 24) | (t << 16) | e) {
-}
+inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e)
+ : id((p << 24) | (t << 16) | e) {}
inline bool ResourceId::isValid() const {
- return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0;
+ return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0;
}
inline uint8_t ResourceId::packageId() const {
- return static_cast<uint8_t>(id >> 24);
+ return static_cast<uint8_t>(id >> 24);
}
inline uint8_t ResourceId::typeId() const {
- return static_cast<uint8_t>(id >> 16);
+ return static_cast<uint8_t>(id >> 16);
}
inline uint16_t ResourceId::entryId() const {
- return static_cast<uint16_t>(id);
+ return static_cast<uint16_t>(id);
}
inline bool operator<(const ResourceId& lhs, const ResourceId& rhs) {
- return lhs.id < rhs.id;
+ return lhs.id < rhs.id;
}
inline bool operator>(const ResourceId& lhs, const ResourceId& rhs) {
- return lhs.id > rhs.id;
+ return lhs.id > rhs.id;
}
inline bool operator==(const ResourceId& lhs, const ResourceId& rhs) {
- return lhs.id == rhs.id;
+ return lhs.id == rhs.id;
}
inline bool operator!=(const ResourceId& lhs, const ResourceId& rhs) {
- return lhs.id != rhs.id;
+ return lhs.id != rhs.id;
}
-inline ::std::ostream& operator<<(::std::ostream& out, const ResourceId& resId) {
- std::ios_base::fmtflags oldFlags = out.flags();
- char oldFill = out.fill();
- out << "0x" << std::internal << std::setfill('0') << std::setw(8)
- << std::hex << resId.id;
- out.flags(oldFlags);
- out.fill(oldFill);
- return out;
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const ResourceId& resId) {
+ std::ios_base::fmtflags oldFlags = out.flags();
+ char oldFill = out.fill();
+ out << "0x" << std::internal << std::setfill('0') << std::setw(8) << std::hex
+ << resId.id;
+ out.flags(oldFlags);
+ out.fill(oldFill);
+ return out;
}
//
// ResourceType implementation.
//
-inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) {
- return out << toString(val);
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const ResourceType& val) {
+ return out << toString(val);
}
//
// ResourceName implementation.
//
-inline ResourceName::ResourceName(const StringPiece& p, ResourceType t, const StringPiece& e) :
- package(p.toString()), type(t), entry(e.toString()) {
-}
+inline ResourceName::ResourceName(const StringPiece& p, ResourceType t,
+ const StringPiece& e)
+ : package(p.toString()), type(t), entry(e.toString()) {}
inline int ResourceName::compare(const ResourceName& other) const {
- int cmp = package.compare(other.package);
- if (cmp != 0) return cmp;
- cmp = static_cast<int>(type) - static_cast<int>(other.type);
- if (cmp != 0) return cmp;
- cmp = entry.compare(other.entry);
- return cmp;
+ int cmp = package.compare(other.package);
+ if (cmp != 0) return cmp;
+ cmp = static_cast<int>(type) - static_cast<int>(other.type);
+ if (cmp != 0) return cmp;
+ cmp = entry.compare(other.entry);
+ return cmp;
}
inline bool ResourceName::isValid() const {
- return !package.empty() && !entry.empty();
+ return !package.empty() && !entry.empty();
}
inline bool operator<(const ResourceName& lhs, const ResourceName& rhs) {
- return std::tie(lhs.package, lhs.type, lhs.entry)
- < std::tie(rhs.package, rhs.type, rhs.entry);
+ return std::tie(lhs.package, lhs.type, lhs.entry) <
+ std::tie(rhs.package, rhs.type, rhs.entry);
}
inline bool operator==(const ResourceName& lhs, const ResourceName& rhs) {
- return std::tie(lhs.package, lhs.type, lhs.entry)
- == std::tie(rhs.package, rhs.type, rhs.entry);
+ return std::tie(lhs.package, lhs.type, lhs.entry) ==
+ std::tie(rhs.package, rhs.type, rhs.entry);
}
inline bool operator!=(const ResourceName& lhs, const ResourceName& rhs) {
- return std::tie(lhs.package, lhs.type, lhs.entry)
- != std::tie(rhs.package, rhs.type, rhs.entry);
+ return std::tie(lhs.package, lhs.type, lhs.entry) !=
+ std::tie(rhs.package, rhs.type, rhs.entry);
}
-inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) {
- if (!name.package.empty()) {
- out << name.package << ":";
- }
- return out << name.type << "/" << name.entry;
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const ResourceName& name) {
+ if (!name.package.empty()) {
+ out << name.package << ":";
+ }
+ return out << name.type << "/" << name.entry;
}
inline std::string ResourceName::toString() const {
- std::stringstream stream;
- stream << *this;
- return stream.str();
+ std::stringstream stream;
+ stream << *this;
+ return stream.str();
}
//
// ResourceNameRef implementation.
//
-inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs) :
- package(rhs.package), type(rhs.type), entry(rhs.entry) {
-}
+inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs)
+ : package(rhs.package), type(rhs.type), entry(rhs.entry) {}
inline ResourceNameRef::ResourceNameRef(const StringPiece& p, ResourceType t,
- const StringPiece& e) :
- package(p), type(t), entry(e) {
-}
+ const StringPiece& e)
+ : package(p), type(t), entry(e) {}
inline ResourceNameRef& ResourceNameRef::operator=(const ResourceName& rhs) {
- package = rhs.package;
- type = rhs.type;
- entry = rhs.entry;
- return *this;
+ package = rhs.package;
+ type = rhs.type;
+ entry = rhs.entry;
+ return *this;
}
inline ResourceName ResourceNameRef::toResourceName() const {
- return ResourceName(package, type, entry);
+ return ResourceName(package, type, entry);
}
inline bool ResourceNameRef::isValid() const {
- return !package.empty() && !entry.empty();
+ return !package.empty() && !entry.empty();
}
inline bool operator<(const ResourceNameRef& lhs, const ResourceNameRef& rhs) {
- return std::tie(lhs.package, lhs.type, lhs.entry)
- < std::tie(rhs.package, rhs.type, rhs.entry);
+ return std::tie(lhs.package, lhs.type, lhs.entry) <
+ std::tie(rhs.package, rhs.type, rhs.entry);
}
inline bool operator==(const ResourceNameRef& lhs, const ResourceNameRef& rhs) {
- return std::tie(lhs.package, lhs.type, lhs.entry)
- == std::tie(rhs.package, rhs.type, rhs.entry);
+ return std::tie(lhs.package, lhs.type, lhs.entry) ==
+ std::tie(rhs.package, rhs.type, rhs.entry);
}
inline bool operator!=(const ResourceNameRef& lhs, const ResourceNameRef& rhs) {
- return std::tie(lhs.package, lhs.type, lhs.entry)
- != std::tie(rhs.package, rhs.type, rhs.entry);
+ return std::tie(lhs.package, lhs.type, lhs.entry) !=
+ std::tie(rhs.package, rhs.type, rhs.entry);
}
-inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNameRef& name) {
- if (!name.package.empty()) {
- out << name.package << ":";
- }
- return out << name.type << "/" << name.entry;
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const ResourceNameRef& name) {
+ if (!name.package.empty()) {
+ out << name.package << ":";
+ }
+ return out << name.type << "/" << name.entry;
}
inline bool operator<(const ResourceName& lhs, const ResourceNameRef& b) {
- return ResourceNameRef(lhs) < b;
+ return ResourceNameRef(lhs) < b;
}
inline bool operator!=(const ResourceName& lhs, const ResourceNameRef& rhs) {
- return ResourceNameRef(lhs) != rhs;
+ return ResourceNameRef(lhs) != rhs;
}
-inline bool operator==(const SourcedResourceName& lhs, const SourcedResourceName& rhs) {
- return lhs.name == rhs.name && lhs.line == rhs.line;
+inline bool operator==(const SourcedResourceName& lhs,
+ const SourcedResourceName& rhs) {
+ return lhs.name == rhs.name && lhs.line == rhs.line;
}
-} // namespace aapt
+} // namespace aapt
namespace std {
-template <> struct hash<aapt::ResourceName> {
- size_t operator()(const aapt::ResourceName& name) const {
- android::hash_t h = 0;
- h = android::JenkinsHashMix(h, hash<string>()(name.package));
- h = android::JenkinsHashMix(h, static_cast<uint32_t>(name.type));
- h = android::JenkinsHashMix(h, hash<string>()(name.entry));
- return static_cast<size_t>(h);
- }
+template <>
+struct hash<aapt::ResourceName> {
+ size_t operator()(const aapt::ResourceName& name) const {
+ android::hash_t h = 0;
+ h = android::JenkinsHashMix(h, hash<string>()(name.package));
+ h = android::JenkinsHashMix(h, static_cast<uint32_t>(name.type));
+ h = android::JenkinsHashMix(h, hash<string>()(name.entry));
+ return static_cast<size_t>(h);
+ }
};
-} // namespace std
+} // namespace std
-#endif // AAPT_RESOURCE_H
+#endif // AAPT_RESOURCE_H
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index c430c46..7d50e1d 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -28,445 +28,488 @@
namespace aapt {
-constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
+constexpr const char* sXliffNamespaceUri =
+ "urn:oasis:names:tc:xliff:document:1.2";
/**
- * Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
+ * Returns true if the element is <skip> or <eat-comment> and can be safely
+ * ignored.
*/
-static bool shouldIgnoreElement(const StringPiece& ns, const StringPiece& name) {
- return ns.empty() && (name == "skip" || name == "eat-comment");
+static bool shouldIgnoreElement(const StringPiece& ns,
+ const StringPiece& name) {
+ return ns.empty() && (name == "skip" || name == "eat-comment");
}
static uint32_t parseFormatType(const StringPiece& piece) {
- if (piece == "reference") return android::ResTable_map::TYPE_REFERENCE;
- else if (piece == "string") return android::ResTable_map::TYPE_STRING;
- else if (piece == "integer") return android::ResTable_map::TYPE_INTEGER;
- else if (piece == "boolean") return android::ResTable_map::TYPE_BOOLEAN;
- else if (piece == "color") return android::ResTable_map::TYPE_COLOR;
- else if (piece == "float") return android::ResTable_map::TYPE_FLOAT;
- else if (piece == "dimension") return android::ResTable_map::TYPE_DIMENSION;
- else if (piece == "fraction") return android::ResTable_map::TYPE_FRACTION;
- else if (piece == "enum") return android::ResTable_map::TYPE_ENUM;
- else if (piece == "flags") return android::ResTable_map::TYPE_FLAGS;
- return 0;
+ if (piece == "reference")
+ return android::ResTable_map::TYPE_REFERENCE;
+ else if (piece == "string")
+ return android::ResTable_map::TYPE_STRING;
+ else if (piece == "integer")
+ return android::ResTable_map::TYPE_INTEGER;
+ else if (piece == "boolean")
+ return android::ResTable_map::TYPE_BOOLEAN;
+ else if (piece == "color")
+ return android::ResTable_map::TYPE_COLOR;
+ else if (piece == "float")
+ return android::ResTable_map::TYPE_FLOAT;
+ else if (piece == "dimension")
+ return android::ResTable_map::TYPE_DIMENSION;
+ else if (piece == "fraction")
+ return android::ResTable_map::TYPE_FRACTION;
+ else if (piece == "enum")
+ return android::ResTable_map::TYPE_ENUM;
+ else if (piece == "flags")
+ return android::ResTable_map::TYPE_FLAGS;
+ return 0;
}
static uint32_t parseFormatAttribute(const StringPiece& str) {
- uint32_t mask = 0;
- for (StringPiece part : util::tokenize(str, '|')) {
- StringPiece trimmedPart = util::trimWhitespace(part);
- uint32_t type = parseFormatType(trimmedPart);
- if (type == 0) {
- return 0;
- }
- mask |= type;
+ uint32_t mask = 0;
+ for (StringPiece part : util::tokenize(str, '|')) {
+ StringPiece trimmedPart = util::trimWhitespace(part);
+ uint32_t type = parseFormatType(trimmedPart);
+ if (type == 0) {
+ return 0;
}
- return mask;
+ mask |= type;
+ }
+ return mask;
}
/**
* A parsed resource ready to be added to the ResourceTable.
*/
struct ParsedResource {
- ResourceName name;
- ConfigDescription config;
- std::string product;
- Source source;
- ResourceId id;
- Maybe<SymbolState> symbolState;
- std::string comment;
- std::unique_ptr<Value> value;
- std::list<ParsedResource> childResources;
+ ResourceName name;
+ ConfigDescription config;
+ std::string product;
+ Source source;
+ ResourceId id;
+ Maybe<SymbolState> symbolState;
+ std::string comment;
+ std::unique_ptr<Value> value;
+ std::list<ParsedResource> childResources;
};
// Recursively adds resources to the ResourceTable.
-static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) {
- StringPiece trimmedComment = util::trimWhitespace(res->comment);
- if (trimmedComment.size() != res->comment.size()) {
- // Only if there was a change do we re-assign.
- res->comment = trimmedComment.toString();
- }
+static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag,
+ ParsedResource* res) {
+ StringPiece trimmedComment = util::trimWhitespace(res->comment);
+ if (trimmedComment.size() != res->comment.size()) {
+ // Only if there was a change do we re-assign.
+ res->comment = trimmedComment.toString();
+ }
- if (res->symbolState) {
- Symbol symbol;
- symbol.state = res->symbolState.value();
- symbol.source = res->source;
- symbol.comment = res->comment;
- if (!table->setSymbolState(res->name, res->id, symbol, diag)) {
- return false;
- }
+ if (res->symbolState) {
+ Symbol symbol;
+ symbol.state = res->symbolState.value();
+ symbol.source = res->source;
+ symbol.comment = res->comment;
+ if (!table->setSymbolState(res->name, res->id, symbol, diag)) {
+ return false;
}
+ }
- if (res->value) {
- // Attach the comment, source and config to the value.
- res->value->setComment(std::move(res->comment));
- res->value->setSource(std::move(res->source));
+ if (res->value) {
+ // Attach the comment, source and config to the value.
+ res->value->setComment(std::move(res->comment));
+ res->value->setSource(std::move(res->source));
- if (!table->addResource(res->name, res->id, res->config, res->product,
- std::move(res->value), diag)) {
- return false;
- }
+ if (!table->addResource(res->name, res->id, res->config, res->product,
+ std::move(res->value), diag)) {
+ return false;
}
+ }
- bool error = false;
- for (ParsedResource& child : res->childResources) {
- error |= !addResourcesToTable(table, diag, &child);
- }
- return !error;
+ bool error = false;
+ for (ParsedResource& child : res->childResources) {
+ error |= !addResourcesToTable(table, diag, &child);
+ }
+ return !error;
}
// Convenient aliases for more readable function calls.
-enum {
- kAllowRawString = true,
- kNoRawString = false
-};
+enum { kAllowRawString = true, kNoRawString = false };
-ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
+ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table,
+ const Source& source,
const ConfigDescription& config,
- const ResourceParserOptions& options) :
- mDiag(diag), mTable(table), mSource(source), mConfig(config), mOptions(options) {
-}
+ const ResourceParserOptions& options)
+ : mDiag(diag),
+ mTable(table),
+ mSource(source),
+ mConfig(config),
+ mOptions(options) {}
/**
* Build a string from XML that converts nested elements into Span objects.
*/
-bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser, std::string* outRawString,
+bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser,
+ std::string* outRawString,
StyleString* outStyleString) {
- std::vector<Span> spanStack;
+ std::vector<Span> spanStack;
- bool error = false;
- outRawString->clear();
- outStyleString->spans.clear();
- util::StringBuilder builder;
- size_t depth = 1;
- while (xml::XmlPullParser::isGoodEvent(parser->next())) {
- const xml::XmlPullParser::Event event = parser->getEvent();
- if (event == xml::XmlPullParser::Event::kEndElement) {
- if (!parser->getElementNamespace().empty()) {
- // We already warned and skipped the start element, so just skip here too
- continue;
- }
+ bool error = false;
+ outRawString->clear();
+ outStyleString->spans.clear();
+ util::StringBuilder builder;
+ size_t depth = 1;
+ while (xml::XmlPullParser::isGoodEvent(parser->next())) {
+ const xml::XmlPullParser::Event event = parser->getEvent();
+ if (event == xml::XmlPullParser::Event::kEndElement) {
+ if (!parser->getElementNamespace().empty()) {
+ // We already warned and skipped the start element, so just skip here
+ // too
+ continue;
+ }
- depth--;
- if (depth == 0) {
- break;
- }
+ depth--;
+ if (depth == 0) {
+ break;
+ }
- spanStack.back().lastChar = builder.utf16Len() - 1;
- outStyleString->spans.push_back(spanStack.back());
- spanStack.pop_back();
+ spanStack.back().lastChar = builder.utf16Len() - 1;
+ outStyleString->spans.push_back(spanStack.back());
+ spanStack.pop_back();
- } else if (event == xml::XmlPullParser::Event::kText) {
- outRawString->append(parser->getText());
- builder.append(parser->getText());
+ } else if (event == xml::XmlPullParser::Event::kText) {
+ outRawString->append(parser->getText());
+ builder.append(parser->getText());
- } else if (event == xml::XmlPullParser::Event::kStartElement) {
- if (!parser->getElementNamespace().empty()) {
- if (parser->getElementNamespace() != sXliffNamespaceUri) {
- // Only warn if this isn't an xliff namespace.
- mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "skipping element '"
- << parser->getElementName()
- << "' with unknown namespace '"
- << parser->getElementNamespace()
- << "'");
- }
- continue;
- }
- depth++;
-
- // Build a span object out of the nested element.
- std::string spanName = parser->getElementName();
- const auto endAttrIter = parser->endAttributes();
- for (auto attrIter = parser->beginAttributes(); attrIter != endAttrIter; ++attrIter) {
- spanName += ";";
- spanName += attrIter->name;
- spanName += "=";
- spanName += attrIter->value;
- }
-
- if (builder.utf16Len() > std::numeric_limits<uint32_t>::max()) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "style string '" << builder.str() << "' is too long");
- error = true;
- } else {
- spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.utf16Len()) });
- }
-
- } else if (event == xml::XmlPullParser::Event::kComment) {
- // Skip
- } else {
- assert(false);
+ } else if (event == xml::XmlPullParser::Event::kStartElement) {
+ if (!parser->getElementNamespace().empty()) {
+ if (parser->getElementNamespace() != sXliffNamespaceUri) {
+ // Only warn if this isn't an xliff namespace.
+ mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "skipping element '" << parser->getElementName()
+ << "' with unknown namespace '"
+ << parser->getElementNamespace() << "'");
}
- }
- assert(spanStack.empty() && "spans haven't been fully processed");
+ continue;
+ }
+ depth++;
- outStyleString->str = builder.str();
- return !error;
+ // Build a span object out of the nested element.
+ std::string spanName = parser->getElementName();
+ const auto endAttrIter = parser->endAttributes();
+ for (auto attrIter = parser->beginAttributes(); attrIter != endAttrIter;
+ ++attrIter) {
+ spanName += ";";
+ spanName += attrIter->name;
+ spanName += "=";
+ spanName += attrIter->value;
+ }
+
+ if (builder.utf16Len() > std::numeric_limits<uint32_t>::max()) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "style string '" << builder.str() << "' is too long");
+ error = true;
+ } else {
+ spanStack.push_back(
+ Span{spanName, static_cast<uint32_t>(builder.utf16Len())});
+ }
+
+ } else if (event == xml::XmlPullParser::Event::kComment) {
+ // Skip
+ } else {
+ assert(false);
+ }
+ }
+ assert(spanStack.empty() && "spans haven't been fully processed");
+
+ outStyleString->str = builder.str();
+ return !error;
}
bool ResourceParser::parse(xml::XmlPullParser* parser) {
- bool error = false;
- const size_t depth = parser->getDepth();
- while (xml::XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
- // Skip comments and text.
- continue;
- }
-
- if (!parser->getElementNamespace().empty() || parser->getElementName() != "resources") {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "root element must be <resources>");
- return false;
- }
-
- error |= !parseResources(parser);
- break;
- };
-
- if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "xml parser error: " << parser->getLastError());
- return false;
+ bool error = false;
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip comments and text.
+ continue;
}
- return !error;
+
+ if (!parser->getElementNamespace().empty() ||
+ parser->getElementName() != "resources") {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "root element must be <resources>");
+ return false;
+ }
+
+ error |= !parseResources(parser);
+ break;
+ };
+
+ if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "xml parser error: " << parser->getLastError());
+ return false;
+ }
+ return !error;
}
bool ResourceParser::parseResources(xml::XmlPullParser* parser) {
- std::set<ResourceName> strippedResources;
+ std::set<ResourceName> strippedResources;
- bool error = false;
- std::string comment;
- const size_t depth = parser->getDepth();
- while (xml::XmlPullParser::nextChildNode(parser, depth)) {
- const xml::XmlPullParser::Event event = parser->getEvent();
- if (event == xml::XmlPullParser::Event::kComment) {
- comment = parser->getComment();
- continue;
- }
-
- if (event == xml::XmlPullParser::Event::kText) {
- if (!util::trimWhitespace(parser->getText()).empty()) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "plain text not allowed here");
- error = true;
- }
- continue;
- }
-
- assert(event == xml::XmlPullParser::Event::kStartElement);
-
- if (!parser->getElementNamespace().empty()) {
- // Skip unknown namespace.
- continue;
- }
-
- std::string elementName = parser->getElementName();
- if (elementName == "skip" || elementName == "eat-comment") {
- comment = "";
- continue;
- }
-
- ParsedResource parsedResource;
- parsedResource.config = mConfig;
- parsedResource.source = mSource.withLine(parser->getLineNumber());
- parsedResource.comment = std::move(comment);
-
- // Extract the product name if it exists.
- if (Maybe<StringPiece> maybeProduct = xml::findNonEmptyAttribute(parser, "product")) {
- parsedResource.product = maybeProduct.value().toString();
- }
-
- // Parse the resource regardless of product.
- if (!parseResource(parser, &parsedResource)) {
- error = true;
- continue;
- }
-
- if (!addResourcesToTable(mTable, mDiag, &parsedResource)) {
- error = true;
- }
+ bool error = false;
+ std::string comment;
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ const xml::XmlPullParser::Event event = parser->getEvent();
+ if (event == xml::XmlPullParser::Event::kComment) {
+ comment = parser->getComment();
+ continue;
}
- // Check that we included at least one variant of each stripped resource.
- for (const ResourceName& strippedResource : strippedResources) {
- if (!mTable->findResource(strippedResource)) {
- // Failed to find the resource.
- mDiag->error(DiagMessage(mSource) << "resource '" << strippedResource << "' "
- "was filtered out but no product variant remains");
- error = true;
- }
+ if (event == xml::XmlPullParser::Event::kText) {
+ if (!util::trimWhitespace(parser->getText()).empty()) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "plain text not allowed here");
+ error = true;
+ }
+ continue;
}
- return !error;
+ assert(event == xml::XmlPullParser::Event::kStartElement);
+
+ if (!parser->getElementNamespace().empty()) {
+ // Skip unknown namespace.
+ continue;
+ }
+
+ std::string elementName = parser->getElementName();
+ if (elementName == "skip" || elementName == "eat-comment") {
+ comment = "";
+ continue;
+ }
+
+ ParsedResource parsedResource;
+ parsedResource.config = mConfig;
+ parsedResource.source = mSource.withLine(parser->getLineNumber());
+ parsedResource.comment = std::move(comment);
+
+ // Extract the product name if it exists.
+ if (Maybe<StringPiece> maybeProduct =
+ xml::findNonEmptyAttribute(parser, "product")) {
+ parsedResource.product = maybeProduct.value().toString();
+ }
+
+ // Parse the resource regardless of product.
+ if (!parseResource(parser, &parsedResource)) {
+ error = true;
+ continue;
+ }
+
+ if (!addResourcesToTable(mTable, mDiag, &parsedResource)) {
+ error = true;
+ }
+ }
+
+ // Check that we included at least one variant of each stripped resource.
+ for (const ResourceName& strippedResource : strippedResources) {
+ if (!mTable->findResource(strippedResource)) {
+ // Failed to find the resource.
+ mDiag->error(DiagMessage(mSource)
+ << "resource '" << strippedResource
+ << "' "
+ "was filtered out but no product variant remains");
+ error = true;
+ }
+ }
+
+ return !error;
}
+bool ResourceParser::parseResource(xml::XmlPullParser* parser,
+ ParsedResource* outResource) {
+ struct ItemTypeFormat {
+ ResourceType type;
+ uint32_t format;
+ };
-bool ResourceParser::parseResource(xml::XmlPullParser* parser, ParsedResource* outResource) {
- struct ItemTypeFormat {
- ResourceType type;
- uint32_t format;
- };
+ using BagParseFunc = std::function<bool(ResourceParser*, xml::XmlPullParser*,
+ ParsedResource*)>;
- using BagParseFunc = std::function<bool(ResourceParser*, xml::XmlPullParser*, ParsedResource*)>;
+ static const auto elToItemMap =
+ ImmutableMap<std::string, ItemTypeFormat>::createPreSorted({
+ {"bool", {ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN}},
+ {"color", {ResourceType::kColor, android::ResTable_map::TYPE_COLOR}},
+ {"dimen",
+ {ResourceType::kDimen, android::ResTable_map::TYPE_FLOAT |
+ android::ResTable_map::TYPE_FRACTION |
+ android::ResTable_map::TYPE_DIMENSION}},
+ {"drawable",
+ {ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR}},
+ {"fraction",
+ {ResourceType::kFraction,
+ android::ResTable_map::TYPE_FLOAT |
+ android::ResTable_map::TYPE_FRACTION |
+ android::ResTable_map::TYPE_DIMENSION}},
+ {"integer",
+ {ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER}},
+ {"string",
+ {ResourceType::kString, android::ResTable_map::TYPE_STRING}},
+ });
- static const auto elToItemMap = ImmutableMap<std::string, ItemTypeFormat>::createPreSorted({
- { "bool", { ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN } },
- { "color", { ResourceType::kColor, android::ResTable_map::TYPE_COLOR } },
- { "dimen", { ResourceType::kDimen, android::ResTable_map::TYPE_FLOAT
- | android::ResTable_map::TYPE_FRACTION
- | android::ResTable_map::TYPE_DIMENSION } },
- { "drawable", { ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR } },
- { "fraction", { ResourceType::kFraction, android::ResTable_map::TYPE_FLOAT
- | android::ResTable_map::TYPE_FRACTION
- | android::ResTable_map::TYPE_DIMENSION } },
- { "integer", { ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER } },
- { "string", { ResourceType::kString, android::ResTable_map::TYPE_STRING } },
- });
+ static const auto elToBagMap =
+ ImmutableMap<std::string, BagParseFunc>::createPreSorted({
+ {"add-resource", std::mem_fn(&ResourceParser::parseAddResource)},
+ {"array", std::mem_fn(&ResourceParser::parseArray)},
+ {"attr", std::mem_fn(&ResourceParser::parseAttr)},
+ {"declare-styleable",
+ std::mem_fn(&ResourceParser::parseDeclareStyleable)},
+ {"integer-array", std::mem_fn(&ResourceParser::parseIntegerArray)},
+ {"java-symbol", std::mem_fn(&ResourceParser::parseSymbol)},
+ {"plurals", std::mem_fn(&ResourceParser::parsePlural)},
+ {"public", std::mem_fn(&ResourceParser::parsePublic)},
+ {"public-group", std::mem_fn(&ResourceParser::parsePublicGroup)},
+ {"string-array", std::mem_fn(&ResourceParser::parseStringArray)},
+ {"style", std::mem_fn(&ResourceParser::parseStyle)},
+ {"symbol", std::mem_fn(&ResourceParser::parseSymbol)},
+ });
- static const auto elToBagMap = ImmutableMap<std::string, BagParseFunc>::createPreSorted({
- { "add-resource", std::mem_fn(&ResourceParser::parseAddResource) },
- { "array", std::mem_fn(&ResourceParser::parseArray) },
- { "attr", std::mem_fn(&ResourceParser::parseAttr) },
- { "declare-styleable", std::mem_fn(&ResourceParser::parseDeclareStyleable) },
- { "integer-array", std::mem_fn(&ResourceParser::parseIntegerArray) },
- { "java-symbol", std::mem_fn(&ResourceParser::parseSymbol) },
- { "plurals", std::mem_fn(&ResourceParser::parsePlural) },
- { "public", std::mem_fn(&ResourceParser::parsePublic) },
- { "public-group", std::mem_fn(&ResourceParser::parsePublicGroup) },
- { "string-array", std::mem_fn(&ResourceParser::parseStringArray) },
- { "style", std::mem_fn(&ResourceParser::parseStyle) },
- { "symbol", std::mem_fn(&ResourceParser::parseSymbol) },
- });
+ std::string resourceType = parser->getElementName();
- std::string resourceType = parser->getElementName();
+ // The value format accepted for this resource.
+ uint32_t resourceFormat = 0u;
- // The value format accepted for this resource.
- uint32_t resourceFormat = 0u;
-
- if (resourceType == "item") {
- // Items have their type encoded in the type attribute.
- if (Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type")) {
- resourceType = maybeType.value().toString();
- } else {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "<item> must have a 'type' attribute");
- return false;
- }
-
- if (Maybe<StringPiece> maybeFormat = xml::findNonEmptyAttribute(parser, "format")) {
- // An explicit format for this resource was specified. The resource will retain
- // its type in its name, but the accepted value for this type is overridden.
- resourceFormat = parseFormatType(maybeFormat.value());
- if (!resourceFormat) {
- mDiag->error(DiagMessage(outResource->source)
- << "'" << maybeFormat.value() << "' is an invalid format");
- return false;
- }
- }
+ if (resourceType == "item") {
+ // Items have their type encoded in the type attribute.
+ if (Maybe<StringPiece> maybeType =
+ xml::findNonEmptyAttribute(parser, "type")) {
+ resourceType = maybeType.value().toString();
+ } else {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "<item> must have a 'type' attribute");
+ return false;
}
- // Get the name of the resource. This will be checked later, because not all
- // XML elements require a name.
- Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
-
- if (resourceType == "id") {
- if (!maybeName) {
- mDiag->error(DiagMessage(outResource->source)
- << "<" << parser->getElementName() << "> missing 'name' attribute");
- return false;
- }
-
- outResource->name.type = ResourceType::kId;
- outResource->name.entry = maybeName.value().toString();
- outResource->value = util::make_unique<Id>();
- return true;
- }
-
- const auto itemIter = elToItemMap.find(resourceType);
- if (itemIter != elToItemMap.end()) {
- // This is an item, record its type and format and start parsing.
-
- if (!maybeName) {
- mDiag->error(DiagMessage(outResource->source)
- << "<" << parser->getElementName() << "> missing 'name' attribute");
- return false;
- }
-
- outResource->name.type = itemIter->second.type;
- outResource->name.entry = maybeName.value().toString();
-
- // Only use the implicit format for this type if it wasn't overridden.
- if (!resourceFormat) {
- resourceFormat = itemIter->second.format;
- }
-
- if (!parseItem(parser, outResource, resourceFormat)) {
- return false;
- }
- return true;
- }
-
- // This might be a bag or something.
- const auto bagIter = elToBagMap.find(resourceType);
- if (bagIter != elToBagMap.end()) {
- // Ensure we have a name (unless this is a <public-group>).
- if (resourceType != "public-group") {
- if (!maybeName) {
- mDiag->error(DiagMessage(outResource->source)
- << "<" << parser->getElementName() << "> missing 'name' attribute");
- return false;
- }
-
- outResource->name.entry = maybeName.value().toString();
- }
-
- // Call the associated parse method. The type will be filled in by the
- // parse func.
- if (!bagIter->second(this, parser, outResource)) {
- return false;
- }
- return true;
- }
-
- // Try parsing the elementName (or type) as a resource. These shall only be
- // resources like 'layout' or 'xml' and they can only be references.
- const ResourceType* parsedType = parseResourceType(resourceType);
- if (parsedType) {
- if (!maybeName) {
- mDiag->error(DiagMessage(outResource->source)
- << "<" << parser->getElementName() << "> missing 'name' attribute");
- return false;
- }
-
- outResource->name.type = *parsedType;
- outResource->name.entry = maybeName.value().toString();
- outResource->value = parseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString);
- if (!outResource->value) {
- mDiag->error(DiagMessage(outResource->source)
- << "invalid value for type '" << *parsedType << "'. Expected a reference");
- return false;
- }
- return true;
- }
-
- mDiag->warn(DiagMessage(outResource->source)
- << "unknown resource type '" << parser->getElementName() << "'");
- return false;
-}
-
-bool ResourceParser::parseItem(xml::XmlPullParser* parser, ParsedResource* outResource,
- const uint32_t format) {
- if (format == android::ResTable_map::TYPE_STRING) {
- return parseString(parser, outResource);
- }
-
- outResource->value = parseXml(parser, format, kNoRawString);
- if (!outResource->value) {
- mDiag->error(DiagMessage(outResource->source) << "invalid " << outResource->name.type);
+ if (Maybe<StringPiece> maybeFormat =
+ xml::findNonEmptyAttribute(parser, "format")) {
+ // An explicit format for this resource was specified. The resource will
+ // retain
+ // its type in its name, but the accepted value for this type is
+ // overridden.
+ resourceFormat = parseFormatType(maybeFormat.value());
+ if (!resourceFormat) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "'" << maybeFormat.value() << "' is an invalid format");
return false;
+ }
+ }
+ }
+
+ // Get the name of the resource. This will be checked later, because not all
+ // XML elements require a name.
+ Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
+
+ if (resourceType == "id") {
+ if (!maybeName) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "<" << parser->getElementName()
+ << "> missing 'name' attribute");
+ return false;
+ }
+
+ outResource->name.type = ResourceType::kId;
+ outResource->name.entry = maybeName.value().toString();
+ outResource->value = util::make_unique<Id>();
+ return true;
+ }
+
+ const auto itemIter = elToItemMap.find(resourceType);
+ if (itemIter != elToItemMap.end()) {
+ // This is an item, record its type and format and start parsing.
+
+ if (!maybeName) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "<" << parser->getElementName()
+ << "> missing 'name' attribute");
+ return false;
+ }
+
+ outResource->name.type = itemIter->second.type;
+ outResource->name.entry = maybeName.value().toString();
+
+ // Only use the implicit format for this type if it wasn't overridden.
+ if (!resourceFormat) {
+ resourceFormat = itemIter->second.format;
+ }
+
+ if (!parseItem(parser, outResource, resourceFormat)) {
+ return false;
}
return true;
+ }
+
+ // This might be a bag or something.
+ const auto bagIter = elToBagMap.find(resourceType);
+ if (bagIter != elToBagMap.end()) {
+ // Ensure we have a name (unless this is a <public-group>).
+ if (resourceType != "public-group") {
+ if (!maybeName) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "<" << parser->getElementName()
+ << "> missing 'name' attribute");
+ return false;
+ }
+
+ outResource->name.entry = maybeName.value().toString();
+ }
+
+ // Call the associated parse method. The type will be filled in by the
+ // parse func.
+ if (!bagIter->second(this, parser, outResource)) {
+ return false;
+ }
+ return true;
+ }
+
+ // Try parsing the elementName (or type) as a resource. These shall only be
+ // resources like 'layout' or 'xml' and they can only be references.
+ const ResourceType* parsedType = parseResourceType(resourceType);
+ if (parsedType) {
+ if (!maybeName) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "<" << parser->getElementName()
+ << "> missing 'name' attribute");
+ return false;
+ }
+
+ outResource->name.type = *parsedType;
+ outResource->name.entry = maybeName.value().toString();
+ outResource->value =
+ parseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString);
+ if (!outResource->value) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "invalid value for type '" << *parsedType
+ << "'. Expected a reference");
+ return false;
+ }
+ return true;
+ }
+
+ mDiag->warn(DiagMessage(outResource->source)
+ << "unknown resource type '" << parser->getElementName() << "'");
+ return false;
+}
+
+bool ResourceParser::parseItem(xml::XmlPullParser* parser,
+ ParsedResource* outResource,
+ const uint32_t format) {
+ if (format == android::ResTable_map::TYPE_STRING) {
+ return parseString(parser, outResource);
+ }
+
+ outResource->value = parseXml(parser, format, kNoRawString);
+ if (!outResource->value) {
+ mDiag->error(DiagMessage(outResource->source) << "invalid "
+ << outResource->name.type);
+ return false;
+ }
+ return true;
}
/**
@@ -476,771 +519,835 @@
* an Item. If allowRawValue is false, nullptr is returned in this
* case.
*/
-std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const uint32_t typeMask,
+std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser,
+ const uint32_t typeMask,
const bool allowRawValue) {
- const size_t beginXmlLine = parser->getLineNumber();
+ const size_t beginXmlLine = parser->getLineNumber();
- std::string rawValue;
- StyleString styleString;
- if (!flattenXmlSubtree(parser, &rawValue, &styleString)) {
- return {};
- }
-
- if (!styleString.spans.empty()) {
- // This can only be a StyledString.
- return util::make_unique<StyledString>(
- mTable->stringPool.makeRef(styleString, StringPool::Context{ 1, mConfig }));
- }
-
- auto onCreateReference = [&](const ResourceName& name) {
- // name.package can be empty here, as it will assume the package name of the table.
- std::unique_ptr<Id> id = util::make_unique<Id>();
- id->setSource(mSource.withLine(beginXmlLine));
- mTable->addResource(name, {}, {}, std::move(id), mDiag);
- };
-
- // Process the raw value.
- std::unique_ptr<Item> processedItem = ResourceUtils::tryParseItemForAttribute(
- rawValue, typeMask, onCreateReference);
- if (processedItem) {
- // Fix up the reference.
- if (Reference* ref = valueCast<Reference>(processedItem.get())) {
- transformReferenceFromNamespace(parser, "", ref);
- }
- return processedItem;
- }
-
- // Try making a regular string.
- if (typeMask & android::ResTable_map::TYPE_STRING) {
- // Use the trimmed, escaped string.
- return util::make_unique<String>(
- mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
- }
-
- if (allowRawValue) {
- // We can't parse this so return a RawString if we are allowed.
- return util::make_unique<RawString>(
- mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
- }
+ std::string rawValue;
+ StyleString styleString;
+ if (!flattenXmlSubtree(parser, &rawValue, &styleString)) {
return {};
+ }
+
+ if (!styleString.spans.empty()) {
+ // This can only be a StyledString.
+ return util::make_unique<StyledString>(mTable->stringPool.makeRef(
+ styleString,
+ StringPool::Context(StringPool::Context::kStylePriority, mConfig)));
+ }
+
+ auto onCreateReference = [&](const ResourceName& name) {
+ // name.package can be empty here, as it will assume the package name of the
+ // table.
+ std::unique_ptr<Id> id = util::make_unique<Id>();
+ id->setSource(mSource.withLine(beginXmlLine));
+ mTable->addResource(name, {}, {}, std::move(id), mDiag);
+ };
+
+ // Process the raw value.
+ std::unique_ptr<Item> processedItem = ResourceUtils::tryParseItemForAttribute(
+ rawValue, typeMask, onCreateReference);
+ if (processedItem) {
+ // Fix up the reference.
+ if (Reference* ref = valueCast<Reference>(processedItem.get())) {
+ transformReferenceFromNamespace(parser, "", ref);
+ }
+ return processedItem;
+ }
+
+ // Try making a regular string.
+ if (typeMask & android::ResTable_map::TYPE_STRING) {
+ // Use the trimmed, escaped string.
+ return util::make_unique<String>(mTable->stringPool.makeRef(
+ styleString.str, StringPool::Context(mConfig)));
+ }
+
+ if (allowRawValue) {
+ // We can't parse this so return a RawString if we are allowed.
+ return util::make_unique<RawString>(
+ mTable->stringPool.makeRef(rawValue, StringPool::Context(mConfig)));
+ }
+ return {};
}
-bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) {
- bool formatted = true;
- if (Maybe<StringPiece> formattedAttr = xml::findAttribute(parser, "formatted")) {
- Maybe<bool> maybeFormatted = ResourceUtils::parseBool(formattedAttr.value());
- if (!maybeFormatted) {
- mDiag->error(DiagMessage(outResource->source)
- << "invalid value for 'formatted'. Must be a boolean");
- return false;
- }
- formatted = maybeFormatted.value();
+bool ResourceParser::parseString(xml::XmlPullParser* parser,
+ ParsedResource* outResource) {
+ bool formatted = true;
+ if (Maybe<StringPiece> formattedAttr =
+ xml::findAttribute(parser, "formatted")) {
+ Maybe<bool> maybeFormatted =
+ ResourceUtils::parseBool(formattedAttr.value());
+ if (!maybeFormatted) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "invalid value for 'formatted'. Must be a boolean");
+ return false;
}
+ formatted = maybeFormatted.value();
+ }
- bool translateable = mOptions.translatable;
- if (Maybe<StringPiece> translateableAttr = xml::findAttribute(parser, "translatable")) {
- Maybe<bool> maybeTranslateable = ResourceUtils::parseBool(translateableAttr.value());
- if (!maybeTranslateable) {
- mDiag->error(DiagMessage(outResource->source)
- << "invalid value for 'translatable'. Must be a boolean");
- return false;
- }
- translateable = maybeTranslateable.value();
+ bool translateable = mOptions.translatable;
+ if (Maybe<StringPiece> translateableAttr =
+ xml::findAttribute(parser, "translatable")) {
+ Maybe<bool> maybeTranslateable =
+ ResourceUtils::parseBool(translateableAttr.value());
+ if (!maybeTranslateable) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "invalid value for 'translatable'. Must be a boolean");
+ return false;
}
+ translateable = maybeTranslateable.value();
+ }
- outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
- if (!outResource->value) {
- mDiag->error(DiagMessage(outResource->source) << "not a valid string");
- return false;
- }
+ outResource->value =
+ parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
+ if (!outResource->value) {
+ mDiag->error(DiagMessage(outResource->source) << "not a valid string");
+ return false;
+ }
- if (String* stringValue = valueCast<String>(outResource->value.get())) {
- stringValue->setTranslateable(translateable);
+ if (String* stringValue = valueCast<String>(outResource->value.get())) {
+ stringValue->setTranslateable(translateable);
- if (formatted && translateable) {
- if (!util::verifyJavaStringFormat(*stringValue->value)) {
- DiagMessage msg(outResource->source);
- msg << "multiple substitutions specified in non-positional format; "
- "did you mean to add the formatted=\"false\" attribute?";
- if (mOptions.errorOnPositionalArguments) {
- mDiag->error(msg);
- return false;
- }
-
- mDiag->warn(msg);
- }
+ if (formatted && translateable) {
+ if (!util::verifyJavaStringFormat(*stringValue->value)) {
+ DiagMessage msg(outResource->source);
+ msg << "multiple substitutions specified in non-positional format; "
+ "did you mean to add the formatted=\"false\" attribute?";
+ if (mOptions.errorOnPositionalArguments) {
+ mDiag->error(msg);
+ return false;
}
- } else if (StyledString* stringValue = valueCast<StyledString>(outResource->value.get())) {
- stringValue->setTranslateable(translateable);
+ mDiag->warn(msg);
+ }
}
- return true;
+
+ } else if (StyledString* stringValue =
+ valueCast<StyledString>(outResource->value.get())) {
+ stringValue->setTranslateable(translateable);
+ }
+ return true;
}
-bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource) {
- Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type");
- if (!maybeType) {
- mDiag->error(DiagMessage(outResource->source) << "<public> must have a 'type' attribute");
- return false;
- }
+bool ResourceParser::parsePublic(xml::XmlPullParser* parser,
+ ParsedResource* outResource) {
+ Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type");
+ if (!maybeType) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "<public> must have a 'type' attribute");
+ return false;
+ }
- const ResourceType* parsedType = parseResourceType(maybeType.value());
- if (!parsedType) {
- mDiag->error(DiagMessage(outResource->source)
- << "invalid resource type '" << maybeType.value() << "' in <public>");
- return false;
- }
+ const ResourceType* parsedType = parseResourceType(maybeType.value());
+ if (!parsedType) {
+ mDiag->error(DiagMessage(outResource->source) << "invalid resource type '"
+ << maybeType.value()
+ << "' in <public>");
+ return false;
+ }
- outResource->name.type = *parsedType;
+ outResource->name.type = *parsedType;
- if (Maybe<StringPiece> maybeIdStr = xml::findNonEmptyAttribute(parser, "id")) {
- Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(maybeIdStr.value());
- if (!maybeId) {
- mDiag->error(DiagMessage(outResource->source)
- << "invalid resource ID '" << maybeId.value() << "' in <public>");
- return false;
- }
- outResource->id = maybeId.value();
- }
-
- if (*parsedType == ResourceType::kId) {
- // An ID marked as public is also the definition of an ID.
- outResource->value = util::make_unique<Id>();
- }
-
- outResource->symbolState = SymbolState::kPublic;
- return true;
-}
-
-bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource) {
- Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type");
- if (!maybeType) {
- mDiag->error(DiagMessage(outResource->source)
- << "<public-group> must have a 'type' attribute");
- return false;
- }
-
- const ResourceType* parsedType = parseResourceType(maybeType.value());
- if (!parsedType) {
- mDiag->error(DiagMessage(outResource->source)
- << "invalid resource type '" << maybeType.value() << "' in <public-group>");
- return false;
- }
-
- Maybe<StringPiece> maybeIdStr = xml::findNonEmptyAttribute(parser, "first-id");
- if (!maybeIdStr) {
- mDiag->error(DiagMessage(outResource->source)
- << "<public-group> must have a 'first-id' attribute");
- return false;
- }
-
- Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(maybeIdStr.value());
+ if (Maybe<StringPiece> maybeIdStr =
+ xml::findNonEmptyAttribute(parser, "id")) {
+ Maybe<ResourceId> maybeId =
+ ResourceUtils::parseResourceId(maybeIdStr.value());
if (!maybeId) {
- mDiag->error(DiagMessage(outResource->source)
- << "invalid resource ID '" << maybeIdStr.value() << "' in <public-group>");
- return false;
+ mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '"
+ << maybeId.value()
+ << "' in <public>");
+ return false;
}
+ outResource->id = maybeId.value();
+ }
- ResourceId nextId = maybeId.value();
+ if (*parsedType == ResourceType::kId) {
+ // An ID marked as public is also the definition of an ID.
+ outResource->value = util::make_unique<Id>();
+ }
- std::string comment;
- bool error = false;
- const size_t depth = parser->getDepth();
- while (xml::XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
- comment = util::trimWhitespace(parser->getComment()).toString();
- continue;
- } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
- // Skip text.
- continue;
- }
-
- const Source itemSource = mSource.withLine(parser->getLineNumber());
- const std::string& elementNamespace = parser->getElementNamespace();
- const std::string& elementName = parser->getElementName();
- if (elementNamespace.empty() && elementName == "public") {
- Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
- if (!maybeName) {
- mDiag->error(DiagMessage(itemSource) << "<public> must have a 'name' attribute");
- error = true;
- continue;
- }
-
- if (xml::findNonEmptyAttribute(parser, "id")) {
- mDiag->error(DiagMessage(itemSource) << "'id' is ignored within <public-group>");
- error = true;
- continue;
- }
-
- if (xml::findNonEmptyAttribute(parser, "type")) {
- mDiag->error(DiagMessage(itemSource) << "'type' is ignored within <public-group>");
- error = true;
- continue;
- }
-
- ParsedResource childResource;
- childResource.name.type = *parsedType;
- childResource.name.entry = maybeName.value().toString();
- childResource.id = nextId;
- childResource.comment = std::move(comment);
- childResource.source = itemSource;
- childResource.symbolState = SymbolState::kPublic;
- outResource->childResources.push_back(std::move(childResource));
-
- nextId.id += 1;
-
- } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
- mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
- error = true;
- }
- }
- return !error;
+ outResource->symbolState = SymbolState::kPublic;
+ return true;
}
-bool ResourceParser::parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource) {
- Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type");
- if (!maybeType) {
- mDiag->error(DiagMessage(outResource->source)
- << "<" << parser->getElementName() << "> must have a 'type' attribute");
- return false;
- }
-
- const ResourceType* parsedType = parseResourceType(maybeType.value());
- if (!parsedType) {
- mDiag->error(DiagMessage(outResource->source)
- << "invalid resource type '" << maybeType.value()
- << "' in <" << parser->getElementName() << ">");
- return false;
- }
-
- outResource->name.type = *parsedType;
- return true;
-}
-
-bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) {
- if (parseSymbolImpl(parser, outResource)) {
- outResource->symbolState = SymbolState::kPrivate;
- return true;
- }
+bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser,
+ ParsedResource* outResource) {
+ Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type");
+ if (!maybeType) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "<public-group> must have a 'type' attribute");
return false;
-}
+ }
-bool ResourceParser::parseAddResource(xml::XmlPullParser* parser, ParsedResource* outResource) {
- if (parseSymbolImpl(parser, outResource)) {
- outResource->symbolState = SymbolState::kUndefined;
- return true;
- }
+ const ResourceType* parsedType = parseResourceType(maybeType.value());
+ if (!parsedType) {
+ mDiag->error(DiagMessage(outResource->source) << "invalid resource type '"
+ << maybeType.value()
+ << "' in <public-group>");
return false;
+ }
+
+ Maybe<StringPiece> maybeIdStr =
+ xml::findNonEmptyAttribute(parser, "first-id");
+ if (!maybeIdStr) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "<public-group> must have a 'first-id' attribute");
+ return false;
+ }
+
+ Maybe<ResourceId> maybeId =
+ ResourceUtils::parseResourceId(maybeIdStr.value());
+ if (!maybeId) {
+ mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '"
+ << maybeIdStr.value()
+ << "' in <public-group>");
+ return false;
+ }
+
+ ResourceId nextId = maybeId.value();
+
+ std::string comment;
+ bool error = false;
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
+ comment = util::trimWhitespace(parser->getComment()).toString();
+ continue;
+ } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text.
+ continue;
+ }
+
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
+ const std::string& elementNamespace = parser->getElementNamespace();
+ const std::string& elementName = parser->getElementName();
+ if (elementNamespace.empty() && elementName == "public") {
+ Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
+ if (!maybeName) {
+ mDiag->error(DiagMessage(itemSource)
+ << "<public> must have a 'name' attribute");
+ error = true;
+ continue;
+ }
+
+ if (xml::findNonEmptyAttribute(parser, "id")) {
+ mDiag->error(DiagMessage(itemSource)
+ << "'id' is ignored within <public-group>");
+ error = true;
+ continue;
+ }
+
+ if (xml::findNonEmptyAttribute(parser, "type")) {
+ mDiag->error(DiagMessage(itemSource)
+ << "'type' is ignored within <public-group>");
+ error = true;
+ continue;
+ }
+
+ ParsedResource childResource;
+ childResource.name.type = *parsedType;
+ childResource.name.entry = maybeName.value().toString();
+ childResource.id = nextId;
+ childResource.comment = std::move(comment);
+ childResource.source = itemSource;
+ childResource.symbolState = SymbolState::kPublic;
+ outResource->childResources.push_back(std::move(childResource));
+
+ nextId.id += 1;
+
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
+ error = true;
+ }
+ }
+ return !error;
}
+bool ResourceParser::parseSymbolImpl(xml::XmlPullParser* parser,
+ ParsedResource* outResource) {
+ Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type");
+ if (!maybeType) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "<" << parser->getElementName()
+ << "> must have a 'type' attribute");
+ return false;
+ }
-bool ResourceParser::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) {
- return parseAttrImpl(parser, outResource, false);
+ const ResourceType* parsedType = parseResourceType(maybeType.value());
+ if (!parsedType) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "invalid resource type '" << maybeType.value() << "' in <"
+ << parser->getElementName() << ">");
+ return false;
+ }
+
+ outResource->name.type = *parsedType;
+ return true;
}
-bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
- bool weak) {
- outResource->name.type = ResourceType::kAttr;
-
- // Attributes only end up in default configuration.
- if (outResource->config != ConfigDescription::defaultConfig()) {
- mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '"
- << outResource->config << "' for attribute " << outResource->name);
- outResource->config = ConfigDescription::defaultConfig();
- }
-
- uint32_t typeMask = 0;
-
- Maybe<StringPiece> maybeFormat = xml::findAttribute(parser, "format");
- if (maybeFormat) {
- typeMask = parseFormatAttribute(maybeFormat.value());
- if (typeMask == 0) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "invalid attribute format '" << maybeFormat.value() << "'");
- return false;
- }
- }
-
- Maybe<int32_t> maybeMin, maybeMax;
-
- if (Maybe<StringPiece> maybeMinStr = xml::findAttribute(parser, "min")) {
- StringPiece minStr = util::trimWhitespace(maybeMinStr.value());
- if (!minStr.empty()) {
- std::u16string minStr16 = util::utf8ToUtf16(minStr);
- android::Res_value value;
- if (android::ResTable::stringToInt(minStr16.data(), minStr16.size(), &value)) {
- maybeMin = static_cast<int32_t>(value.data);
- }
- }
-
- if (!maybeMin) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "invalid 'min' value '" << minStr << "'");
- return false;
- }
- }
-
- if (Maybe<StringPiece> maybeMaxStr = xml::findAttribute(parser, "max")) {
- StringPiece maxStr = util::trimWhitespace(maybeMaxStr.value());
- if (!maxStr.empty()) {
- std::u16string maxStr16 = util::utf8ToUtf16(maxStr);
- android::Res_value value;
- if (android::ResTable::stringToInt(maxStr16.data(), maxStr16.size(), &value)) {
- maybeMax = static_cast<int32_t>(value.data);
- }
- }
-
- if (!maybeMax) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "invalid 'max' value '" << maxStr << "'");
- return false;
- }
- }
-
- if ((maybeMin || maybeMax) && (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "'min' and 'max' can only be used when format='integer'");
- return false;
- }
-
- 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::string comment;
- bool error = false;
- const size_t depth = parser->getDepth();
- while (xml::XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
- comment = util::trimWhitespace(parser->getComment()).toString();
- continue;
- } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
- // Skip text.
- continue;
- }
-
- const Source itemSource = mSource.withLine(parser->getLineNumber());
- const std::string& elementNamespace = parser->getElementNamespace();
- const std::string& elementName = parser->getElementName();
- if (elementNamespace.empty() && (elementName == "flag" || elementName == "enum")) {
- if (elementName == "enum") {
- if (typeMask & android::ResTable_map::TYPE_FLAGS) {
- mDiag->error(DiagMessage(itemSource)
- << "can not define an <enum>; already defined a <flag>");
- error = true;
- continue;
- }
- typeMask |= android::ResTable_map::TYPE_ENUM;
-
- } else if (elementName == "flag") {
- if (typeMask & android::ResTable_map::TYPE_ENUM) {
- mDiag->error(DiagMessage(itemSource)
- << "can not define a <flag>; already defined an <enum>");
- error = true;
- continue;
- }
- typeMask |= android::ResTable_map::TYPE_FLAGS;
- }
-
- if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) {
- Attribute::Symbol& symbol = s.value();
- ParsedResource childResource;
- childResource.name = symbol.symbol.name.value();
- childResource.source = itemSource;
- childResource.value = util::make_unique<Id>();
- outResource->childResources.push_back(std::move(childResource));
-
- 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;
- }
- } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
- mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
- error = true;
- }
-
- comment = {};
- }
-
- if (error) {
- return false;
- }
-
- std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
- attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
- attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
- if (maybeMin) {
- attr->minInt = maybeMin.value();
- }
-
- if (maybeMax) {
- attr->maxInt = maybeMax.value();
- }
- outResource->value = std::move(attr);
+bool ResourceParser::parseSymbol(xml::XmlPullParser* parser,
+ ParsedResource* outResource) {
+ if (parseSymbolImpl(parser, outResource)) {
+ outResource->symbolState = SymbolState::kPrivate;
return true;
+ }
+ return false;
}
-Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(xml::XmlPullParser* parser,
- const StringPiece& tag) {
- const Source source = mSource.withLine(parser->getLineNumber());
+bool ResourceParser::parseAddResource(xml::XmlPullParser* parser,
+ ParsedResource* outResource) {
+ if (parseSymbolImpl(parser, outResource)) {
+ outResource->symbolState = SymbolState::kUndefined;
+ return true;
+ }
+ return false;
+}
- Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
- if (!maybeName) {
- mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">");
- return {};
+bool ResourceParser::parseAttr(xml::XmlPullParser* parser,
+ ParsedResource* outResource) {
+ return parseAttrImpl(parser, outResource, false);
+}
+
+bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser,
+ ParsedResource* outResource, bool weak) {
+ outResource->name.type = ResourceType::kAttr;
+
+ // Attributes only end up in default configuration.
+ if (outResource->config != ConfigDescription::defaultConfig()) {
+ mDiag->warn(DiagMessage(outResource->source)
+ << "ignoring configuration '" << outResource->config
+ << "' for attribute " << outResource->name);
+ outResource->config = ConfigDescription::defaultConfig();
+ }
+
+ uint32_t typeMask = 0;
+
+ Maybe<StringPiece> maybeFormat = xml::findAttribute(parser, "format");
+ if (maybeFormat) {
+ typeMask = parseFormatAttribute(maybeFormat.value());
+ if (typeMask == 0) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "invalid attribute format '" << maybeFormat.value()
+ << "'");
+ return false;
+ }
+ }
+
+ Maybe<int32_t> maybeMin, maybeMax;
+
+ if (Maybe<StringPiece> maybeMinStr = xml::findAttribute(parser, "min")) {
+ StringPiece minStr = util::trimWhitespace(maybeMinStr.value());
+ if (!minStr.empty()) {
+ std::u16string minStr16 = util::utf8ToUtf16(minStr);
+ android::Res_value value;
+ if (android::ResTable::stringToInt(minStr16.data(), minStr16.size(),
+ &value)) {
+ maybeMin = static_cast<int32_t>(value.data);
+ }
}
- Maybe<StringPiece> maybeValue = xml::findNonEmptyAttribute(parser, "value");
- if (!maybeValue) {
- mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">");
- return {};
+ if (!maybeMin) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "invalid 'min' value '" << minStr << "'");
+ return false;
+ }
+ }
+
+ if (Maybe<StringPiece> maybeMaxStr = xml::findAttribute(parser, "max")) {
+ StringPiece maxStr = util::trimWhitespace(maybeMaxStr.value());
+ if (!maxStr.empty()) {
+ std::u16string maxStr16 = util::utf8ToUtf16(maxStr);
+ android::Res_value value;
+ if (android::ResTable::stringToInt(maxStr16.data(), maxStr16.size(),
+ &value)) {
+ maybeMax = static_cast<int32_t>(value.data);
+ }
}
- std::u16string value16 = util::utf8ToUtf16(maybeValue.value());
- android::Res_value val;
- if (!android::ResTable::stringToInt(value16.data(), value16.size(), &val)) {
- mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value()
- << "' for <" << tag << ">; must be an integer");
- return {};
+ if (!maybeMax) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "invalid 'max' value '" << maxStr << "'");
+ return false;
+ }
+ }
+
+ if ((maybeMin || maybeMax) &&
+ (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "'min' and 'max' can only be used when format='integer'");
+ return false;
+ }
+
+ 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::string comment;
+ bool error = false;
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
+ comment = util::trimWhitespace(parser->getComment()).toString();
+ continue;
+ } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text.
+ continue;
}
- return Attribute::Symbol{
- Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())), val.data };
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
+ const std::string& elementNamespace = parser->getElementNamespace();
+ const std::string& elementName = parser->getElementName();
+ if (elementNamespace.empty() &&
+ (elementName == "flag" || elementName == "enum")) {
+ if (elementName == "enum") {
+ if (typeMask & android::ResTable_map::TYPE_FLAGS) {
+ mDiag->error(DiagMessage(itemSource)
+ << "can not define an <enum>; already defined a <flag>");
+ error = true;
+ continue;
+ }
+ typeMask |= android::ResTable_map::TYPE_ENUM;
+
+ } else if (elementName == "flag") {
+ if (typeMask & android::ResTable_map::TYPE_ENUM) {
+ mDiag->error(DiagMessage(itemSource)
+ << "can not define a <flag>; already defined an <enum>");
+ error = true;
+ continue;
+ }
+ typeMask |= android::ResTable_map::TYPE_FLAGS;
+ }
+
+ if (Maybe<Attribute::Symbol> s =
+ parseEnumOrFlagItem(parser, elementName)) {
+ Attribute::Symbol& symbol = s.value();
+ ParsedResource childResource;
+ childResource.name = symbol.symbol.name.value();
+ childResource.source = itemSource;
+ childResource.value = util::make_unique<Id>();
+ outResource->childResources.push_back(std::move(childResource));
+
+ 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;
+ }
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
+ error = true;
+ }
+
+ comment = {};
+ }
+
+ if (error) {
+ return false;
+ }
+
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
+ attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
+ attr->typeMask =
+ typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
+ if (maybeMin) {
+ attr->minInt = maybeMin.value();
+ }
+
+ if (maybeMax) {
+ attr->maxInt = maybeMax.value();
+ }
+ outResource->value = std::move(attr);
+ return true;
+}
+
+Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(
+ xml::XmlPullParser* parser, const StringPiece& tag) {
+ const Source source = mSource.withLine(parser->getLineNumber());
+
+ Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
+ if (!maybeName) {
+ mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <"
+ << tag << ">");
+ return {};
+ }
+
+ Maybe<StringPiece> maybeValue = xml::findNonEmptyAttribute(parser, "value");
+ if (!maybeValue) {
+ mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <"
+ << tag << ">");
+ return {};
+ }
+
+ std::u16string value16 = util::utf8ToUtf16(maybeValue.value());
+ android::Res_value val;
+ if (!android::ResTable::stringToInt(value16.data(), value16.size(), &val)) {
+ mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value()
+ << "' for <" << tag
+ << ">; must be an integer");
+ return {};
+ }
+
+ return Attribute::Symbol{
+ Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())),
+ val.data};
}
bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) {
- const Source source = mSource.withLine(parser->getLineNumber());
+ const Source source = mSource.withLine(parser->getLineNumber());
- Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
- if (!maybeName) {
- mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute");
+ Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
+ if (!maybeName) {
+ mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute");
+ return false;
+ }
+
+ Maybe<Reference> maybeKey =
+ ResourceUtils::parseXmlAttributeName(maybeName.value());
+ if (!maybeKey) {
+ mDiag->error(DiagMessage(source) << "invalid attribute name '"
+ << maybeName.value() << "'");
+ return false;
+ }
+
+ transformReferenceFromNamespace(parser, "", &maybeKey.value());
+ maybeKey.value().setSource(source);
+
+ std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
+ if (!value) {
+ mDiag->error(DiagMessage(source) << "could not parse style item");
+ return false;
+ }
+
+ style->entries.push_back(
+ Style::Entry{std::move(maybeKey.value()), std::move(value)});
+ return true;
+}
+
+bool ResourceParser::parseStyle(xml::XmlPullParser* parser,
+ ParsedResource* outResource) {
+ outResource->name.type = ResourceType::kStyle;
+
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+
+ Maybe<StringPiece> maybeParent = xml::findAttribute(parser, "parent");
+ if (maybeParent) {
+ // If the parent is empty, we don't have a parent, but we also don't infer
+ // either.
+ if (!maybeParent.value().empty()) {
+ std::string errStr;
+ style->parent = ResourceUtils::parseStyleParentReference(
+ maybeParent.value(), &errStr);
+ if (!style->parent) {
+ mDiag->error(DiagMessage(outResource->source) << errStr);
return false;
+ }
+
+ // Transform the namespace prefix to the actual package name, and mark the
+ // reference as
+ // private if appropriate.
+ transformReferenceFromNamespace(parser, "", &style->parent.value());
}
- Maybe<Reference> maybeKey = ResourceUtils::parseXmlAttributeName(maybeName.value());
- if (!maybeKey) {
- mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'");
- return false;
+ } else {
+ // No parent was specified, so try inferring it from the style name.
+ std::string styleName = outResource->name.entry;
+ size_t pos = styleName.find_last_of(u'.');
+ if (pos != std::string::npos) {
+ style->parentInferred = true;
+ style->parent = Reference(
+ ResourceName({}, ResourceType::kStyle, styleName.substr(0, pos)));
+ }
+ }
+
+ bool error = false;
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text and comments.
+ continue;
}
- transformReferenceFromNamespace(parser, "", &maybeKey.value());
- maybeKey.value().setSource(source);
+ const std::string& elementNamespace = parser->getElementNamespace();
+ const std::string& elementName = parser->getElementName();
+ if (elementNamespace == "" && elementName == "item") {
+ error |= !parseStyleItem(parser, style.get());
- std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
- if (!value) {
- mDiag->error(DiagMessage(source) << "could not parse style item");
- return false;
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << ":" << elementName << ">");
+ error = true;
}
+ }
- style->entries.push_back(Style::Entry{ std::move(maybeKey.value()), std::move(value) });
- return true;
+ if (error) {
+ return false;
+ }
+
+ outResource->value = std::move(style);
+ return true;
}
-bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) {
- outResource->name.type = ResourceType::kStyle;
-
- std::unique_ptr<Style> style = util::make_unique<Style>();
-
- Maybe<StringPiece> maybeParent = xml::findAttribute(parser, "parent");
- if (maybeParent) {
- // If the parent is empty, we don't have a parent, but we also don't infer either.
- if (!maybeParent.value().empty()) {
- std::string errStr;
- style->parent = ResourceUtils::parseStyleParentReference(maybeParent.value(), &errStr);
- if (!style->parent) {
- mDiag->error(DiagMessage(outResource->source) << errStr);
- return false;
- }
-
- // Transform the namespace prefix to the actual package name, and mark the reference as
- // private if appropriate.
- transformReferenceFromNamespace(parser, "", &style->parent.value());
- }
-
- } else {
- // No parent was specified, so try inferring it from the style name.
- std::string styleName = outResource->name.entry;
- size_t pos = styleName.find_last_of(u'.');
- if (pos != std::string::npos) {
- style->parentInferred = true;
- style->parent = Reference(ResourceName({}, ResourceType::kStyle,
- styleName.substr(0, pos)));
- }
- }
-
- bool error = false;
- const size_t depth = parser->getDepth();
- while (xml::XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
- // Skip text and comments.
- continue;
- }
-
- const std::string& elementNamespace = parser->getElementNamespace();
- const std::string& elementName = parser->getElementName();
- if (elementNamespace == "" && elementName == "item") {
- error |= !parseStyleItem(parser, style.get());
-
- } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << ":" << elementName << ">");
- error = true;
- }
- }
-
- if (error) {
- return false;
- }
-
- outResource->value = std::move(style);
- return true;
+bool ResourceParser::parseArray(xml::XmlPullParser* parser,
+ ParsedResource* outResource) {
+ return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_ANY);
}
-bool ResourceParser::parseArray(xml::XmlPullParser* parser, ParsedResource* outResource) {
- return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_ANY);
+bool ResourceParser::parseIntegerArray(xml::XmlPullParser* parser,
+ ParsedResource* outResource) {
+ return parseArrayImpl(parser, outResource,
+ android::ResTable_map::TYPE_INTEGER);
}
-bool ResourceParser::parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource) {
- return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_INTEGER);
+bool ResourceParser::parseStringArray(xml::XmlPullParser* parser,
+ ParsedResource* outResource) {
+ return parseArrayImpl(parser, outResource,
+ android::ResTable_map::TYPE_STRING);
}
-bool ResourceParser::parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource) {
- return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_STRING);
-}
-
-bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
+bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser,
+ ParsedResource* outResource,
const uint32_t typeMask) {
- outResource->name.type = ResourceType::kArray;
+ outResource->name.type = ResourceType::kArray;
- std::unique_ptr<Array> array = util::make_unique<Array>();
+ std::unique_ptr<Array> array = util::make_unique<Array>();
- bool translateable = mOptions.translatable;
- if (Maybe<StringPiece> translateableAttr = xml::findAttribute(parser, "translatable")) {
- Maybe<bool> maybeTranslateable = ResourceUtils::parseBool(translateableAttr.value());
- if (!maybeTranslateable) {
- mDiag->error(DiagMessage(outResource->source)
- << "invalid value for 'translatable'. Must be a boolean");
- return false;
- }
- translateable = maybeTranslateable.value();
+ bool translateable = mOptions.translatable;
+ if (Maybe<StringPiece> translateableAttr =
+ xml::findAttribute(parser, "translatable")) {
+ Maybe<bool> maybeTranslateable =
+ ResourceUtils::parseBool(translateableAttr.value());
+ if (!maybeTranslateable) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "invalid value for 'translatable'. Must be a boolean");
+ return false;
}
- array->setTranslateable(translateable);
+ translateable = maybeTranslateable.value();
+ }
+ array->setTranslateable(translateable);
-
- bool error = false;
- const size_t depth = parser->getDepth();
- while (xml::XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
- // Skip text and comments.
- continue;
- }
-
- const Source itemSource = mSource.withLine(parser->getLineNumber());
- const std::string& elementNamespace = parser->getElementNamespace();
- const std::string& elementName = parser->getElementName();
- if (elementNamespace.empty() && elementName == "item") {
- std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
- if (!item) {
- mDiag->error(DiagMessage(itemSource) << "could not parse array item");
- error = true;
- continue;
- }
- item->setSource(itemSource);
- array->items.emplace_back(std::move(item));
-
- } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "unknown tag <" << elementNamespace << ":" << elementName << ">");
- error = true;
- }
+ bool error = false;
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text and comments.
+ continue;
}
- if (error) {
- return false;
- }
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
+ const std::string& elementNamespace = parser->getElementNamespace();
+ const std::string& elementName = parser->getElementName();
+ if (elementNamespace.empty() && elementName == "item") {
+ std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
+ if (!item) {
+ mDiag->error(DiagMessage(itemSource) << "could not parse array item");
+ error = true;
+ continue;
+ }
+ item->setSource(itemSource);
+ array->items.emplace_back(std::move(item));
- outResource->value = std::move(array);
- return true;
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "unknown tag <" << elementNamespace << ":" << elementName
+ << ">");
+ error = true;
+ }
+ }
+
+ if (error) {
+ return false;
+ }
+
+ outResource->value = std::move(array);
+ return true;
}
-bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource) {
- outResource->name.type = ResourceType::kPlurals;
+bool ResourceParser::parsePlural(xml::XmlPullParser* parser,
+ ParsedResource* outResource) {
+ outResource->name.type = ResourceType::kPlurals;
- std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
- bool error = false;
- const size_t depth = parser->getDepth();
- while (xml::XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
- // Skip text and comments.
- continue;
- }
-
- const Source itemSource = mSource.withLine(parser->getLineNumber());
- const std::string& elementNamespace = parser->getElementNamespace();
- const std::string& elementName = parser->getElementName();
- if (elementNamespace.empty() && elementName == "item") {
- Maybe<StringPiece> maybeQuantity = xml::findNonEmptyAttribute(parser, "quantity");
- if (!maybeQuantity) {
- mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute "
- << "'quantity'");
- error = true;
- continue;
- }
-
- StringPiece trimmedQuantity = util::trimWhitespace(maybeQuantity.value());
- size_t index = 0;
- if (trimmedQuantity == "zero") {
- index = Plural::Zero;
- } else if (trimmedQuantity == "one") {
- index = Plural::One;
- } else if (trimmedQuantity == "two") {
- index = Plural::Two;
- } else if (trimmedQuantity == "few") {
- index = Plural::Few;
- } else if (trimmedQuantity == "many") {
- index = Plural::Many;
- } else if (trimmedQuantity == "other") {
- index = Plural::Other;
- } else {
- mDiag->error(DiagMessage(itemSource)
- << "<item> in <plural> has invalid value '" << trimmedQuantity
- << "' for attribute 'quantity'");
- error = true;
- continue;
- }
-
- if (plural->values[index]) {
- mDiag->error(DiagMessage(itemSource)
- << "duplicate quantity '" << trimmedQuantity << "'");
- error = true;
- continue;
- }
-
- if (!(plural->values[index] = parseXml(parser, android::ResTable_map::TYPE_STRING,
- kNoRawString))) {
- error = true;
- }
- plural->values[index]->setSource(itemSource);
-
- } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
- mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":"
- << elementName << ">");
- error = true;
- }
+ bool error = false;
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text and comments.
+ continue;
}
- if (error) {
- return false;
- }
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
+ const std::string& elementNamespace = parser->getElementNamespace();
+ const std::string& elementName = parser->getElementName();
+ if (elementNamespace.empty() && elementName == "item") {
+ Maybe<StringPiece> maybeQuantity =
+ xml::findNonEmptyAttribute(parser, "quantity");
+ if (!maybeQuantity) {
+ mDiag->error(DiagMessage(itemSource)
+ << "<item> in <plurals> requires attribute "
+ << "'quantity'");
+ error = true;
+ continue;
+ }
- outResource->value = std::move(plural);
- return true;
+ StringPiece trimmedQuantity = util::trimWhitespace(maybeQuantity.value());
+ size_t index = 0;
+ if (trimmedQuantity == "zero") {
+ index = Plural::Zero;
+ } else if (trimmedQuantity == "one") {
+ index = Plural::One;
+ } else if (trimmedQuantity == "two") {
+ index = Plural::Two;
+ } else if (trimmedQuantity == "few") {
+ index = Plural::Few;
+ } else if (trimmedQuantity == "many") {
+ index = Plural::Many;
+ } else if (trimmedQuantity == "other") {
+ index = Plural::Other;
+ } else {
+ mDiag->error(DiagMessage(itemSource)
+ << "<item> in <plural> has invalid value '"
+ << trimmedQuantity << "' for attribute 'quantity'");
+ error = true;
+ continue;
+ }
+
+ if (plural->values[index]) {
+ mDiag->error(DiagMessage(itemSource) << "duplicate quantity '"
+ << trimmedQuantity << "'");
+ error = true;
+ continue;
+ }
+
+ if (!(plural->values[index] = parseXml(
+ parser, android::ResTable_map::TYPE_STRING, kNoRawString))) {
+ error = true;
+ }
+ plural->values[index]->setSource(itemSource);
+
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(itemSource)
+ << "unknown tag <" << elementNamespace << ":" << elementName
+ << ">");
+ error = true;
+ }
+ }
+
+ if (error) {
+ return false;
+ }
+
+ outResource->value = std::move(plural);
+ return true;
}
bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser,
ParsedResource* outResource) {
- outResource->name.type = ResourceType::kStyleable;
+ outResource->name.type = ResourceType::kStyleable;
- // Declare-styleable is kPrivate by default, because it technically only exists in R.java.
- outResource->symbolState = SymbolState::kPublic;
+ // Declare-styleable is kPrivate by default, because it technically only
+ // exists in R.java.
+ outResource->symbolState = SymbolState::kPublic;
- // Declare-styleable only ends up in default config;
- if (outResource->config != ConfigDescription::defaultConfig()) {
- mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '"
- << outResource->config << "' for styleable "
- << outResource->name.entry);
- outResource->config = ConfigDescription::defaultConfig();
+ // Declare-styleable only ends up in default config;
+ if (outResource->config != ConfigDescription::defaultConfig()) {
+ mDiag->warn(DiagMessage(outResource->source)
+ << "ignoring configuration '" << outResource->config
+ << "' for styleable " << outResource->name.entry);
+ outResource->config = ConfigDescription::defaultConfig();
+ }
+
+ std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+
+ std::string comment;
+ bool error = false;
+ const size_t depth = parser->getDepth();
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
+ comment = util::trimWhitespace(parser->getComment()).toString();
+ continue;
+ } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
+ // Ignore text.
+ continue;
}
- std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
+ const std::string& elementNamespace = parser->getElementNamespace();
+ const std::string& elementName = parser->getElementName();
+ if (elementNamespace.empty() && elementName == "attr") {
+ Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
+ if (!maybeName) {
+ mDiag->error(DiagMessage(itemSource)
+ << "<attr> tag must have a 'name' attribute");
+ error = true;
+ continue;
+ }
- std::string comment;
- bool error = false;
- const size_t depth = parser->getDepth();
- while (xml::XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
- comment = util::trimWhitespace(parser->getComment()).toString();
- continue;
- } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
- // Ignore text.
- continue;
- }
+ // If this is a declaration, the package name may be in the name. Separate
+ // these out.
+ // Eg. <attr name="android:text" />
+ Maybe<Reference> maybeRef =
+ ResourceUtils::parseXmlAttributeName(maybeName.value());
+ if (!maybeRef) {
+ mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '"
+ << maybeName.value() << "'");
+ error = true;
+ continue;
+ }
- const Source itemSource = mSource.withLine(parser->getLineNumber());
- const std::string& elementNamespace = parser->getElementNamespace();
- const std::string& elementName = parser->getElementName();
- if (elementNamespace.empty() && elementName == "attr") {
- Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
- if (!maybeName) {
- mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute");
- error = true;
- continue;
- }
+ Reference& childRef = maybeRef.value();
+ xml::transformReferenceFromNamespace(parser, "", &childRef);
- // If this is a declaration, the package name may be in the name. Separate these out.
- // Eg. <attr name="android:text" />
- Maybe<Reference> maybeRef = ResourceUtils::parseXmlAttributeName(maybeName.value());
- if (!maybeRef) {
- mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '"
- << maybeName.value() << "'");
- error = true;
- continue;
- }
+ // Create the ParsedResource that will add the attribute to the table.
+ ParsedResource childResource;
+ childResource.name = childRef.name.value();
+ childResource.source = itemSource;
+ childResource.comment = std::move(comment);
- Reference& childRef = maybeRef.value();
- xml::transformReferenceFromNamespace(parser, "", &childRef);
+ if (!parseAttrImpl(parser, &childResource, true)) {
+ error = true;
+ continue;
+ }
- // Create the ParsedResource that will add the attribute to the table.
- ParsedResource childResource;
- childResource.name = childRef.name.value();
- childResource.source = itemSource;
- childResource.comment = std::move(comment);
+ // Create the reference to this attribute.
+ childRef.setComment(childResource.comment);
+ childRef.setSource(itemSource);
+ styleable->entries.push_back(std::move(childRef));
- if (!parseAttrImpl(parser, &childResource, true)) {
- error = true;
- continue;
- }
+ outResource->childResources.push_back(std::move(childResource));
- // Create the reference to this attribute.
- childRef.setComment(childResource.comment);
- childRef.setSource(itemSource);
- styleable->entries.push_back(std::move(childRef));
-
- outResource->childResources.push_back(std::move(childResource));
-
- } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
- mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":"
- << elementName << ">");
- error = true;
- }
-
- comment = {};
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(itemSource)
+ << "unknown tag <" << elementNamespace << ":" << elementName
+ << ">");
+ error = true;
}
- if (error) {
- return false;
- }
+ comment = {};
+ }
- outResource->value = std::move(styleable);
- return true;
+ if (error) {
+ return false;
+ }
+
+ outResource->value = std::move(styleable);
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index ece3090..644ed49 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -33,79 +33,92 @@
struct ParsedResource;
struct ResourceParserOptions {
- /**
- * Whether the default setting for this parser is to allow translation.
- */
- bool translatable = true;
+ /**
+ * Whether the default setting for this parser is to allow translation.
+ */
+ bool translatable = true;
- /**
- * Whether positional arguments in formatted strings are treated as errors or warnings.
- */
- bool errorOnPositionalArguments = true;
+ /**
+ * Whether positional arguments in formatted strings are treated as errors or
+ * warnings.
+ */
+ bool errorOnPositionalArguments = true;
};
/*
* Parses an XML file for resources and adds them to a ResourceTable.
*/
class ResourceParser {
-public:
- ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
- const ConfigDescription& config, const ResourceParserOptions& options = {});
+ public:
+ ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
+ const ConfigDescription& config,
+ const ResourceParserOptions& options = {});
- ResourceParser(const ResourceParser&) = delete; // No copy.
+ ResourceParser(const ResourceParser&) = delete; // No copy.
- bool parse(xml::XmlPullParser* parser);
+ bool parse(xml::XmlPullParser* parser);
-private:
- /*
- * Parses the XML subtree as a StyleString (flattened XML representation for strings
- * with formatting). If successful, `outStyleString`
- * contains the escaped and whitespace trimmed text, while `outRawString`
- * contains the unescaped text. Returns true on success.
- */
- bool flattenXmlSubtree(xml::XmlPullParser* parser, std::string* outRawString,
- StyleString* outStyleString);
+ private:
+ /*
+ * Parses the XML subtree as a StyleString (flattened XML representation for
+ * strings
+ * with formatting). If successful, `outStyleString`
+ * contains the escaped and whitespace trimmed text, while `outRawString`
+ * contains the unescaped text. Returns true on success.
+ */
+ bool flattenXmlSubtree(xml::XmlPullParser* parser, std::string* outRawString,
+ StyleString* outStyleString);
- /*
- * Parses the XML subtree and returns an Item.
- * The type of Item that can be parsed is denoted by the `typeMask`.
- * If `allowRawValue` is true and the subtree can not be parsed as a regular Item, then a
- * RawString is returned. Otherwise this returns false;
- */
- std::unique_ptr<Item> parseXml(xml::XmlPullParser* parser, const uint32_t typeMask,
- const bool allowRawValue);
+ /*
+ * Parses the XML subtree and returns an Item.
+ * The type of Item that can be parsed is denoted by the `typeMask`.
+ * If `allowRawValue` is true and the subtree can not be parsed as a regular
+ * Item, then a
+ * RawString is returned. Otherwise this returns false;
+ */
+ std::unique_ptr<Item> parseXml(xml::XmlPullParser* parser,
+ const uint32_t typeMask,
+ const bool allowRawValue);
- bool parseResources(xml::XmlPullParser* parser);
- bool parseResource(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseResources(xml::XmlPullParser* parser);
+ bool parseResource(xml::XmlPullParser* parser, ParsedResource* outResource);
- bool parseItem(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t format);
- bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseItem(xml::XmlPullParser* parser, ParsedResource* outResource,
+ uint32_t format);
+ bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource);
- bool parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource);
- bool parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource);
- bool parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource);
- bool parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource);
- bool parseAddResource(xml::XmlPullParser* parser, ParsedResource* outResource);
- bool parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource);
- bool parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, bool weak);
- Maybe<Attribute::Symbol> parseEnumOrFlagItem(xml::XmlPullParser* parser,
- const StringPiece& tag);
- bool parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource);
- bool parseStyleItem(xml::XmlPullParser* parser, Style* style);
- bool parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource);
- bool parseArray(xml::XmlPullParser* parser, ParsedResource* outResource);
- bool parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource);
- bool parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource);
- bool parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask);
- bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parsePublicGroup(xml::XmlPullParser* parser,
+ ParsedResource* outResource);
+ bool parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseAddResource(xml::XmlPullParser* parser,
+ ParsedResource* outResource);
+ bool parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
+ bool weak);
+ Maybe<Attribute::Symbol> parseEnumOrFlagItem(xml::XmlPullParser* parser,
+ const StringPiece& tag);
+ bool parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseStyleItem(xml::XmlPullParser* parser, Style* style);
+ bool parseDeclareStyleable(xml::XmlPullParser* parser,
+ ParsedResource* outResource);
+ bool parseArray(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseIntegerArray(xml::XmlPullParser* parser,
+ ParsedResource* outResource);
+ bool parseStringArray(xml::XmlPullParser* parser,
+ ParsedResource* outResource);
+ bool parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
+ uint32_t typeMask);
+ bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource);
- IDiagnostics* mDiag;
- ResourceTable* mTable;
- Source mSource;
- ConfigDescription mConfig;
- ResourceParserOptions mOptions;
+ IDiagnostics* mDiag;
+ ResourceTable* mTable;
+ Source mSource;
+ ConfigDescription mConfig;
+ ResourceParserOptions mOptions;
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_RESOURCE_PARSER_H
+#endif // AAPT_RESOURCE_PARSER_H
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index e097740..b6d57c0 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -26,512 +26,559 @@
namespace aapt {
-constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+constexpr const char* kXmlPreamble =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::stringstream input(kXmlPreamble);
- input << "<attr name=\"foo\"/>" << std::endl;
- ResourceTable table;
- ResourceParser parser(context->getDiagnostics(), &table, Source{ "test" }, {});
- xml::XmlPullParser xmlParser(input);
- ASSERT_FALSE(parser.parse(&xmlParser));
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::stringstream input(kXmlPreamble);
+ input << "<attr name=\"foo\"/>" << std::endl;
+ ResourceTable table;
+ ResourceParser parser(context->getDiagnostics(), &table, Source{"test"}, {});
+ xml::XmlPullParser xmlParser(input);
+ ASSERT_FALSE(parser.parse(&xmlParser));
}
struct ResourceParserTest : public ::testing::Test {
- ResourceTable mTable;
- std::unique_ptr<IAaptContext> mContext;
+ ResourceTable mTable;
+ std::unique_ptr<IAaptContext> mContext;
- void SetUp() override {
- mContext = test::ContextBuilder().build();
- }
+ void SetUp() override { mContext = test::ContextBuilder().build(); }
- ::testing::AssertionResult testParse(const StringPiece& str) {
- return testParse(str, ConfigDescription{});
- }
+ ::testing::AssertionResult testParse(const StringPiece& str) {
+ return testParse(str, ConfigDescription{});
+ }
- ::testing::AssertionResult testParse(const StringPiece& str, const ConfigDescription& config) {
- std::stringstream input(kXmlPreamble);
- input << "<resources>\n" << str << "\n</resources>" << std::endl;
- ResourceParserOptions parserOptions;
- ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, config,
- parserOptions);
- xml::XmlPullParser xmlParser(input);
- if (parser.parse(&xmlParser)) {
- return ::testing::AssertionSuccess();
- }
- return ::testing::AssertionFailure();
+ ::testing::AssertionResult testParse(const StringPiece& str,
+ const ConfigDescription& config) {
+ std::stringstream input(kXmlPreamble);
+ input << "<resources>\n" << str << "\n</resources>" << std::endl;
+ ResourceParserOptions parserOptions;
+ ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{"test"},
+ config, parserOptions);
+ xml::XmlPullParser xmlParser(input);
+ if (parser.parse(&xmlParser)) {
+ return ::testing::AssertionSuccess();
}
+ return ::testing::AssertionFailure();
+ }
};
TEST_F(ResourceParserTest, ParseQuotedString) {
- std::string input = "<string name=\"foo\"> \" hey there \" </string>";
- ASSERT_TRUE(testParse(input));
+ std::string input = "<string name=\"foo\"> \" hey there \" </string>";
+ ASSERT_TRUE(testParse(input));
- String* str = test::getValue<String>(&mTable, "string/foo");
- ASSERT_NE(nullptr, str);
- EXPECT_EQ(std::string(" hey there "), *str->value);
+ String* str = test::getValue<String>(&mTable, "string/foo");
+ ASSERT_NE(nullptr, str);
+ EXPECT_EQ(std::string(" hey there "), *str->value);
}
TEST_F(ResourceParserTest, ParseEscapedString) {
- std::string input = "<string name=\"foo\">\\?123</string>";
- ASSERT_TRUE(testParse(input));
+ std::string input = "<string name=\"foo\">\\?123</string>";
+ ASSERT_TRUE(testParse(input));
- String* str = test::getValue<String>(&mTable, "string/foo");
- ASSERT_NE(nullptr, str);
- EXPECT_EQ(std::string("?123"), *str->value);
+ String* str = test::getValue<String>(&mTable, "string/foo");
+ ASSERT_NE(nullptr, str);
+ EXPECT_EQ(std::string("?123"), *str->value);
}
TEST_F(ResourceParserTest, ParseFormattedString) {
- std::string input = "<string name=\"foo\">%d %s</string>";
- ASSERT_FALSE(testParse(input));
+ std::string input = "<string name=\"foo\">%d %s</string>";
+ ASSERT_FALSE(testParse(input));
- input = "<string name=\"foo\">%1$d %2$s</string>";
- ASSERT_TRUE(testParse(input));
+ input = "<string name=\"foo\">%1$d %2$s</string>";
+ ASSERT_TRUE(testParse(input));
}
TEST_F(ResourceParserTest, ParseStyledString) {
- // Use a surrogate pair unicode point so that we can verify that the span indices
- // use UTF-16 length and not UTF-18 length.
- std::string input = "<string name=\"foo\">This is my aunt\u2019s <b>string</b></string>";
- ASSERT_TRUE(testParse(input));
+ // Use a surrogate pair unicode point so that we can verify that the span
+ // indices
+ // use UTF-16 length and not UTF-18 length.
+ std::string input =
+ "<string name=\"foo\">This is my aunt\u2019s <b>string</b></string>";
+ ASSERT_TRUE(testParse(input));
- StyledString* str = test::getValue<StyledString>(&mTable, "string/foo");
- ASSERT_NE(nullptr, str);
+ StyledString* str = test::getValue<StyledString>(&mTable, "string/foo");
+ ASSERT_NE(nullptr, str);
- const std::string expectedStr = "This is my aunt\u2019s string";
- EXPECT_EQ(expectedStr, *str->value->str);
- EXPECT_EQ(1u, str->value->spans.size());
+ const std::string expectedStr = "This is my aunt\u2019s string";
+ EXPECT_EQ(expectedStr, *str->value->str);
+ EXPECT_EQ(1u, str->value->spans.size());
- EXPECT_EQ(std::string("b"), *str->value->spans[0].name);
- EXPECT_EQ(17u, str->value->spans[0].firstChar);
- EXPECT_EQ(23u, str->value->spans[0].lastChar);
+ EXPECT_EQ(std::string("b"), *str->value->spans[0].name);
+ EXPECT_EQ(17u, str->value->spans[0].firstChar);
+ EXPECT_EQ(23u, str->value->spans[0].lastChar);
}
TEST_F(ResourceParserTest, ParseStringWithWhitespace) {
- std::string input = "<string name=\"foo\"> This is what I think </string>";
- ASSERT_TRUE(testParse(input));
+ std::string input = "<string name=\"foo\"> This is what I think </string>";
+ ASSERT_TRUE(testParse(input));
- String* str = test::getValue<String>(&mTable, "string/foo");
- ASSERT_NE(nullptr, str);
- EXPECT_EQ(std::string("This is what I think"), *str->value);
+ String* str = test::getValue<String>(&mTable, "string/foo");
+ ASSERT_NE(nullptr, str);
+ EXPECT_EQ(std::string("This is what I think"), *str->value);
- input = "<string name=\"foo2\">\" This is what I think \"</string>";
- ASSERT_TRUE(testParse(input));
+ input = "<string name=\"foo2\">\" This is what I think \"</string>";
+ ASSERT_TRUE(testParse(input));
- str = test::getValue<String>(&mTable, "string/foo2");
- ASSERT_NE(nullptr, str);
- EXPECT_EQ(std::string(" This is what I think "), *str->value);
+ str = test::getValue<String>(&mTable, "string/foo2");
+ ASSERT_NE(nullptr, str);
+ EXPECT_EQ(std::string(" This is what I think "), *str->value);
}
TEST_F(ResourceParserTest, IgnoreXliffTags) {
- std::string input = "<string name=\"foo\" \n"
- " xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
- " There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<string name=\"foo\" \n"
+ " xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
+ " There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>";
+ ASSERT_TRUE(testParse(input));
- String* str = test::getValue<String>(&mTable, "string/foo");
- ASSERT_NE(nullptr, str);
- EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value));
+ String* str = test::getValue<String>(&mTable, "string/foo");
+ ASSERT_NE(nullptr, str);
+ EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value));
}
TEST_F(ResourceParserTest, ParseNull) {
- std::string input = "<integer name=\"foo\">@null</integer>";
- ASSERT_TRUE(testParse(input));
+ std::string input = "<integer name=\"foo\">@null</integer>";
+ ASSERT_TRUE(testParse(input));
- // The Android runtime treats a value of android::Res_value::TYPE_NULL as
- // a non-existing value, and this causes problems in styles when trying to resolve
- // an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE
- // with a data value of 0.
- BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, "integer/foo");
- ASSERT_NE(nullptr, integer);
- EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType);
- EXPECT_EQ(0u, integer->value.data);
+ // The Android runtime treats a value of android::Res_value::TYPE_NULL as
+ // a non-existing value, and this causes problems in styles when trying to
+ // resolve
+ // an attribute. Null values must be encoded as
+ // android::Res_value::TYPE_REFERENCE
+ // with a data value of 0.
+ BinaryPrimitive* integer =
+ test::getValue<BinaryPrimitive>(&mTable, "integer/foo");
+ ASSERT_NE(nullptr, integer);
+ EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE),
+ integer->value.dataType);
+ EXPECT_EQ(0u, integer->value.data);
}
TEST_F(ResourceParserTest, ParseEmpty) {
- std::string input = "<integer name=\"foo\">@empty</integer>";
- ASSERT_TRUE(testParse(input));
+ std::string input = "<integer name=\"foo\">@empty</integer>";
+ ASSERT_TRUE(testParse(input));
- BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, "integer/foo");
- ASSERT_NE(nullptr, integer);
- EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType);
- EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data);
+ BinaryPrimitive* integer =
+ test::getValue<BinaryPrimitive>(&mTable, "integer/foo");
+ ASSERT_NE(nullptr, integer);
+ EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType);
+ EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data);
}
TEST_F(ResourceParserTest, ParseAttr) {
- std::string input = "<attr name=\"foo\" format=\"string\"/>\n"
- "<attr name=\"bar\"/>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<attr name=\"foo\" format=\"string\"/>\n"
+ "<attr name=\"bar\"/>";
+ ASSERT_TRUE(testParse(input));
- Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
+ Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
- attr = test::getValue<Attribute>(&mTable, "attr/bar");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
+ attr = test::getValue<Attribute>(&mTable, "attr/bar");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
}
-// Old AAPT allowed attributes to be defined under different configurations, but ultimately
-// stored them with the default configuration. Check that we have the same behavior.
-TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) {
- const ConfigDescription watchConfig = test::parseConfigOrDie("watch");
- std::string input = R"EOF(
+// Old AAPT allowed attributes to be defined under different configurations, but
+// ultimately
+// stored them with the default configuration. Check that we have the same
+// behavior.
+TEST_F(ResourceParserTest,
+ ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) {
+ const ConfigDescription watchConfig = test::parseConfigOrDie("watch");
+ std::string input = R"EOF(
<attr name="foo" />
<declare-styleable name="bar">
<attr name="baz" />
</declare-styleable>)EOF";
- ASSERT_TRUE(testParse(input, watchConfig));
+ ASSERT_TRUE(testParse(input, watchConfig));
- EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/foo", watchConfig));
- EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/baz", watchConfig));
- EXPECT_EQ(nullptr, test::getValueForConfig<Styleable>(&mTable, "styleable/bar", watchConfig));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/foo",
+ watchConfig));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/baz",
+ watchConfig));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Styleable>(
+ &mTable, "styleable/bar", watchConfig));
- EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/foo"));
- EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/baz"));
- EXPECT_NE(nullptr, test::getValue<Styleable>(&mTable, "styleable/bar"));
+ EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/foo"));
+ EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/baz"));
+ EXPECT_NE(nullptr, test::getValue<Styleable>(&mTable, "styleable/bar"));
}
TEST_F(ResourceParserTest, ParseAttrWithMinMax) {
- std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>";
+ ASSERT_TRUE(testParse(input));
- Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->typeMask);
- EXPECT_EQ(10, attr->minInt);
- EXPECT_EQ(23, attr->maxInt);
+ Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->typeMask);
+ EXPECT_EQ(10, attr->minInt);
+ EXPECT_EQ(23, attr->maxInt);
}
TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) {
- std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>";
- ASSERT_FALSE(testParse(input));
+ std::string input =
+ "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>";
+ ASSERT_FALSE(testParse(input));
}
TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) {
- std::string input = "<declare-styleable name=\"Styleable\">\n"
- " <attr name=\"foo\" />\n"
- "</declare-styleable>\n"
- "<attr name=\"foo\" format=\"string\"/>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<declare-styleable name=\"Styleable\">\n"
+ " <attr name=\"foo\" />\n"
+ "</declare-styleable>\n"
+ "<attr name=\"foo\" format=\"string\"/>";
+ ASSERT_TRUE(testParse(input));
- Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
+ Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
}
TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) {
- std::string input = "<declare-styleable name=\"Theme\">"
- " <attr name=\"foo\" />\n"
- "</declare-styleable>\n"
- "<declare-styleable name=\"Window\">\n"
- " <attr name=\"foo\" format=\"boolean\"/>\n"
- "</declare-styleable>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<declare-styleable name=\"Theme\">"
+ " <attr name=\"foo\" />\n"
+ "</declare-styleable>\n"
+ "<declare-styleable name=\"Window\">\n"
+ " <attr name=\"foo\" format=\"boolean\"/>\n"
+ "</declare-styleable>";
+ ASSERT_TRUE(testParse(input));
- Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask);
+ Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask);
}
TEST_F(ResourceParserTest, ParseEnumAttr) {
- std::string input = "<attr name=\"foo\">\n"
- " <enum name=\"bar\" value=\"0\"/>\n"
- " <enum name=\"bat\" value=\"1\"/>\n"
- " <enum name=\"baz\" value=\"2\"/>\n"
- "</attr>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<attr name=\"foo\">\n"
+ " <enum name=\"bar\" value=\"0\"/>\n"
+ " <enum name=\"bat\" value=\"1\"/>\n"
+ " <enum name=\"baz\" value=\"2\"/>\n"
+ "</attr>";
+ ASSERT_TRUE(testParse(input));
- Attribute* enumAttr = test::getValue<Attribute>(&mTable, "attr/foo");
- ASSERT_NE(enumAttr, nullptr);
- EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM);
- ASSERT_EQ(enumAttr->symbols.size(), 3u);
+ Attribute* enumAttr = test::getValue<Attribute>(&mTable, "attr/foo");
+ ASSERT_NE(enumAttr, nullptr);
+ EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM);
+ ASSERT_EQ(enumAttr->symbols.size(), 3u);
- AAPT_ASSERT_TRUE(enumAttr->symbols[0].symbol.name);
- EXPECT_EQ(enumAttr->symbols[0].symbol.name.value().entry, "bar");
- EXPECT_EQ(enumAttr->symbols[0].value, 0u);
+ AAPT_ASSERT_TRUE(enumAttr->symbols[0].symbol.name);
+ EXPECT_EQ(enumAttr->symbols[0].symbol.name.value().entry, "bar");
+ EXPECT_EQ(enumAttr->symbols[0].value, 0u);
- AAPT_ASSERT_TRUE(enumAttr->symbols[1].symbol.name);
- EXPECT_EQ(enumAttr->symbols[1].symbol.name.value().entry, "bat");
- EXPECT_EQ(enumAttr->symbols[1].value, 1u);
+ AAPT_ASSERT_TRUE(enumAttr->symbols[1].symbol.name);
+ EXPECT_EQ(enumAttr->symbols[1].symbol.name.value().entry, "bat");
+ EXPECT_EQ(enumAttr->symbols[1].value, 1u);
- AAPT_ASSERT_TRUE(enumAttr->symbols[2].symbol.name);
- EXPECT_EQ(enumAttr->symbols[2].symbol.name.value().entry, "baz");
- EXPECT_EQ(enumAttr->symbols[2].value, 2u);
+ AAPT_ASSERT_TRUE(enumAttr->symbols[2].symbol.name);
+ EXPECT_EQ(enumAttr->symbols[2].symbol.name.value().entry, "baz");
+ EXPECT_EQ(enumAttr->symbols[2].value, 2u);
}
TEST_F(ResourceParserTest, ParseFlagAttr) {
- std::string input = "<attr name=\"foo\">\n"
- " <flag name=\"bar\" value=\"0\"/>\n"
- " <flag name=\"bat\" value=\"1\"/>\n"
- " <flag name=\"baz\" value=\"2\"/>\n"
- "</attr>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<attr name=\"foo\">\n"
+ " <flag name=\"bar\" value=\"0\"/>\n"
+ " <flag name=\"bat\" value=\"1\"/>\n"
+ " <flag name=\"baz\" value=\"2\"/>\n"
+ "</attr>";
+ ASSERT_TRUE(testParse(input));
- Attribute* flagAttr = test::getValue<Attribute>(&mTable, "attr/foo");
- ASSERT_NE(nullptr, flagAttr);
- EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS);
- ASSERT_EQ(flagAttr->symbols.size(), 3u);
+ Attribute* flagAttr = test::getValue<Attribute>(&mTable, "attr/foo");
+ ASSERT_NE(nullptr, flagAttr);
+ EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS);
+ ASSERT_EQ(flagAttr->symbols.size(), 3u);
- AAPT_ASSERT_TRUE(flagAttr->symbols[0].symbol.name);
- EXPECT_EQ(flagAttr->symbols[0].symbol.name.value().entry, "bar");
- EXPECT_EQ(flagAttr->symbols[0].value, 0u);
+ AAPT_ASSERT_TRUE(flagAttr->symbols[0].symbol.name);
+ EXPECT_EQ(flagAttr->symbols[0].symbol.name.value().entry, "bar");
+ EXPECT_EQ(flagAttr->symbols[0].value, 0u);
- AAPT_ASSERT_TRUE(flagAttr->symbols[1].symbol.name);
- EXPECT_EQ(flagAttr->symbols[1].symbol.name.value().entry, "bat");
- EXPECT_EQ(flagAttr->symbols[1].value, 1u);
+ AAPT_ASSERT_TRUE(flagAttr->symbols[1].symbol.name);
+ EXPECT_EQ(flagAttr->symbols[1].symbol.name.value().entry, "bat");
+ EXPECT_EQ(flagAttr->symbols[1].value, 1u);
- AAPT_ASSERT_TRUE(flagAttr->symbols[2].symbol.name);
- EXPECT_EQ(flagAttr->symbols[2].symbol.name.value().entry, "baz");
- EXPECT_EQ(flagAttr->symbols[2].value, 2u);
+ AAPT_ASSERT_TRUE(flagAttr->symbols[2].symbol.name);
+ EXPECT_EQ(flagAttr->symbols[2].symbol.name.value().entry, "baz");
+ EXPECT_EQ(flagAttr->symbols[2].value, 2u);
- std::unique_ptr<BinaryPrimitive> flagValue = ResourceUtils::tryParseFlagSymbol(flagAttr,
- "baz|bat");
- ASSERT_NE(nullptr, flagValue);
- EXPECT_EQ(flagValue->value.data, 1u | 2u);
+ std::unique_ptr<BinaryPrimitive> flagValue =
+ ResourceUtils::tryParseFlagSymbol(flagAttr, "baz|bat");
+ ASSERT_NE(nullptr, flagValue);
+ EXPECT_EQ(flagValue->value.data, 1u | 2u);
}
TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) {
- std::string input = "<attr name=\"foo\">\n"
- " <enum name=\"bar\" value=\"0\"/>\n"
- " <enum name=\"bat\" value=\"1\"/>\n"
- " <enum name=\"bat\" value=\"2\"/>\n"
- "</attr>";
- ASSERT_FALSE(testParse(input));
+ std::string input =
+ "<attr name=\"foo\">\n"
+ " <enum name=\"bar\" value=\"0\"/>\n"
+ " <enum name=\"bat\" value=\"1\"/>\n"
+ " <enum name=\"bat\" value=\"2\"/>\n"
+ "</attr>";
+ ASSERT_FALSE(testParse(input));
}
TEST_F(ResourceParserTest, ParseStyle) {
- std::string input = "<style name=\"foo\" parent=\"@style/fu\">\n"
- " <item name=\"bar\">#ffffffff</item>\n"
- " <item name=\"bat\">@string/hey</item>\n"
- " <item name=\"baz\"><b>hey</b></item>\n"
- "</style>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<style name=\"foo\" parent=\"@style/fu\">\n"
+ " <item name=\"bar\">#ffffffff</item>\n"
+ " <item name=\"bat\">@string/hey</item>\n"
+ " <item name=\"baz\"><b>hey</b></item>\n"
+ "</style>";
+ ASSERT_TRUE(testParse(input));
- Style* style = test::getValue<Style>(&mTable, "style/foo");
- ASSERT_NE(nullptr, style);
- AAPT_ASSERT_TRUE(style->parent);
- AAPT_ASSERT_TRUE(style->parent.value().name);
- EXPECT_EQ(test::parseNameOrDie("style/fu"), style->parent.value().name.value());
- ASSERT_EQ(3u, style->entries.size());
+ Style* style = test::getValue<Style>(&mTable, "style/foo");
+ ASSERT_NE(nullptr, style);
+ AAPT_ASSERT_TRUE(style->parent);
+ AAPT_ASSERT_TRUE(style->parent.value().name);
+ EXPECT_EQ(test::parseNameOrDie("style/fu"),
+ style->parent.value().name.value());
+ ASSERT_EQ(3u, style->entries.size());
- AAPT_ASSERT_TRUE(style->entries[0].key.name);
- EXPECT_EQ(test::parseNameOrDie("attr/bar"), style->entries[0].key.name.value());
+ AAPT_ASSERT_TRUE(style->entries[0].key.name);
+ EXPECT_EQ(test::parseNameOrDie("attr/bar"),
+ style->entries[0].key.name.value());
- AAPT_ASSERT_TRUE(style->entries[1].key.name);
- EXPECT_EQ(test::parseNameOrDie("attr/bat"), style->entries[1].key.name.value());
+ AAPT_ASSERT_TRUE(style->entries[1].key.name);
+ EXPECT_EQ(test::parseNameOrDie("attr/bat"),
+ style->entries[1].key.name.value());
- AAPT_ASSERT_TRUE(style->entries[2].key.name);
- EXPECT_EQ(test::parseNameOrDie("attr/baz"), style->entries[2].key.name.value());
+ AAPT_ASSERT_TRUE(style->entries[2].key.name);
+ EXPECT_EQ(test::parseNameOrDie("attr/baz"),
+ style->entries[2].key.name.value());
}
TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) {
- std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>";
- ASSERT_TRUE(testParse(input));
+ std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>";
+ ASSERT_TRUE(testParse(input));
- Style* style = test::getValue<Style>(&mTable, "style/foo");
- ASSERT_NE(nullptr, style);
- AAPT_ASSERT_TRUE(style->parent);
- AAPT_ASSERT_TRUE(style->parent.value().name);
- EXPECT_EQ(test::parseNameOrDie("com.app:style/Theme"), style->parent.value().name.value());
+ Style* style = test::getValue<Style>(&mTable, "style/foo");
+ ASSERT_NE(nullptr, style);
+ AAPT_ASSERT_TRUE(style->parent);
+ AAPT_ASSERT_TRUE(style->parent.value().name);
+ EXPECT_EQ(test::parseNameOrDie("com.app:style/Theme"),
+ style->parent.value().name.value());
}
TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) {
- std::string input = "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n"
- " name=\"foo\" parent=\"app:Theme\"/>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n"
+ " name=\"foo\" parent=\"app:Theme\"/>";
+ ASSERT_TRUE(testParse(input));
- Style* style = test::getValue<Style>(&mTable, "style/foo");
- ASSERT_NE(nullptr, style);
- AAPT_ASSERT_TRUE(style->parent);
- AAPT_ASSERT_TRUE(style->parent.value().name);
- EXPECT_EQ(test::parseNameOrDie("android:style/Theme"), style->parent.value().name.value());
+ Style* style = test::getValue<Style>(&mTable, "style/foo");
+ ASSERT_NE(nullptr, style);
+ AAPT_ASSERT_TRUE(style->parent);
+ AAPT_ASSERT_TRUE(style->parent.value().name);
+ EXPECT_EQ(test::parseNameOrDie("android:style/Theme"),
+ style->parent.value().name.value());
}
TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) {
- std::string input =
- "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" name=\"foo\">\n"
- " <item name=\"app:bar\">0</item>\n"
- "</style>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" "
+ "name=\"foo\">\n"
+ " <item name=\"app:bar\">0</item>\n"
+ "</style>";
+ ASSERT_TRUE(testParse(input));
- Style* style = test::getValue<Style>(&mTable, "style/foo");
- ASSERT_NE(nullptr, style);
- ASSERT_EQ(1u, style->entries.size());
- EXPECT_EQ(test::parseNameOrDie("android:attr/bar"), style->entries[0].key.name.value());
+ Style* style = test::getValue<Style>(&mTable, "style/foo");
+ ASSERT_NE(nullptr, style);
+ ASSERT_EQ(1u, style->entries.size());
+ EXPECT_EQ(test::parseNameOrDie("android:attr/bar"),
+ style->entries[0].key.name.value());
}
TEST_F(ResourceParserTest, ParseStyleWithInferredParent) {
- std::string input = "<style name=\"foo.bar\"/>";
- ASSERT_TRUE(testParse(input));
+ std::string input = "<style name=\"foo.bar\"/>";
+ ASSERT_TRUE(testParse(input));
- Style* style = test::getValue<Style>(&mTable, "style/foo.bar");
- ASSERT_NE(nullptr, style);
- AAPT_ASSERT_TRUE(style->parent);
- AAPT_ASSERT_TRUE(style->parent.value().name);
- EXPECT_EQ(style->parent.value().name.value(), test::parseNameOrDie("style/foo"));
- EXPECT_TRUE(style->parentInferred);
+ Style* style = test::getValue<Style>(&mTable, "style/foo.bar");
+ ASSERT_NE(nullptr, style);
+ AAPT_ASSERT_TRUE(style->parent);
+ AAPT_ASSERT_TRUE(style->parent.value().name);
+ EXPECT_EQ(style->parent.value().name.value(),
+ test::parseNameOrDie("style/foo"));
+ EXPECT_TRUE(style->parentInferred);
}
-TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAttribute) {
- std::string input = "<style name=\"foo.bar\" parent=\"\"/>";
- ASSERT_TRUE(testParse(input));
+TEST_F(ResourceParserTest,
+ ParseStyleWithInferredParentOverridenByEmptyParentAttribute) {
+ std::string input = "<style name=\"foo.bar\" parent=\"\"/>";
+ ASSERT_TRUE(testParse(input));
- Style* style = test::getValue<Style>(&mTable, "style/foo.bar");
- ASSERT_NE(nullptr, style);
- AAPT_EXPECT_FALSE(style->parent);
- EXPECT_FALSE(style->parentInferred);
+ Style* style = test::getValue<Style>(&mTable, "style/foo.bar");
+ ASSERT_NE(nullptr, style);
+ AAPT_EXPECT_FALSE(style->parent);
+ EXPECT_FALSE(style->parentInferred);
}
TEST_F(ResourceParserTest, ParseStyleWithPrivateParentReference) {
- std::string input = R"EOF(<style name="foo" parent="*android:style/bar" />)EOF";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ R"EOF(<style name="foo" parent="*android:style/bar" />)EOF";
+ ASSERT_TRUE(testParse(input));
- Style* style = test::getValue<Style>(&mTable, "style/foo");
- ASSERT_NE(nullptr, style);
- AAPT_ASSERT_TRUE(style->parent);
- EXPECT_TRUE(style->parent.value().privateReference);
+ Style* style = test::getValue<Style>(&mTable, "style/foo");
+ ASSERT_NE(nullptr, style);
+ AAPT_ASSERT_TRUE(style->parent);
+ EXPECT_TRUE(style->parent.value().privateReference);
}
TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) {
- std::string input = "<string name=\"foo\">@+id/bar</string>";
- ASSERT_TRUE(testParse(input));
+ std::string input = "<string name=\"foo\">@+id/bar</string>";
+ ASSERT_TRUE(testParse(input));
- Id* id = test::getValue<Id>(&mTable, "id/bar");
- ASSERT_NE(id, nullptr);
+ Id* id = test::getValue<Id>(&mTable, "id/bar");
+ ASSERT_NE(id, nullptr);
}
TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) {
- std::string input = "<declare-styleable name=\"foo\">\n"
- " <attr name=\"bar\" />\n"
- " <attr name=\"bat\" format=\"string|reference\"/>\n"
- " <attr name=\"baz\">\n"
- " <enum name=\"foo\" value=\"1\"/>\n"
- " </attr>\n"
- "</declare-styleable>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<declare-styleable name=\"foo\">\n"
+ " <attr name=\"bar\" />\n"
+ " <attr name=\"bat\" format=\"string|reference\"/>\n"
+ " <attr name=\"baz\">\n"
+ " <enum name=\"foo\" value=\"1\"/>\n"
+ " </attr>\n"
+ "</declare-styleable>";
+ ASSERT_TRUE(testParse(input));
- Maybe<ResourceTable::SearchResult> result =
- mTable.findResource(test::parseNameOrDie("styleable/foo"));
- AAPT_ASSERT_TRUE(result);
- EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state);
+ Maybe<ResourceTable::SearchResult> result =
+ mTable.findResource(test::parseNameOrDie("styleable/foo"));
+ AAPT_ASSERT_TRUE(result);
+ EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state);
- Attribute* attr = test::getValue<Attribute>(&mTable, "attr/bar");
- ASSERT_NE(attr, nullptr);
- EXPECT_TRUE(attr->isWeak());
+ Attribute* attr = test::getValue<Attribute>(&mTable, "attr/bar");
+ ASSERT_NE(attr, nullptr);
+ EXPECT_TRUE(attr->isWeak());
- attr = test::getValue<Attribute>(&mTable, "attr/bat");
- ASSERT_NE(attr, nullptr);
- EXPECT_TRUE(attr->isWeak());
+ attr = test::getValue<Attribute>(&mTable, "attr/bat");
+ ASSERT_NE(attr, nullptr);
+ EXPECT_TRUE(attr->isWeak());
- attr = test::getValue<Attribute>(&mTable, "attr/baz");
- ASSERT_NE(attr, nullptr);
- EXPECT_TRUE(attr->isWeak());
- EXPECT_EQ(1u, attr->symbols.size());
+ attr = test::getValue<Attribute>(&mTable, "attr/baz");
+ ASSERT_NE(attr, nullptr);
+ EXPECT_TRUE(attr->isWeak());
+ EXPECT_EQ(1u, attr->symbols.size());
- EXPECT_NE(nullptr, test::getValue<Id>(&mTable, "id/foo"));
+ EXPECT_NE(nullptr, test::getValue<Id>(&mTable, "id/foo"));
- Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo");
- ASSERT_NE(styleable, nullptr);
- ASSERT_EQ(3u, styleable->entries.size());
+ Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo");
+ ASSERT_NE(styleable, nullptr);
+ ASSERT_EQ(3u, styleable->entries.size());
- EXPECT_EQ(test::parseNameOrDie("attr/bar"), styleable->entries[0].name.value());
- EXPECT_EQ(test::parseNameOrDie("attr/bat"), styleable->entries[1].name.value());
+ EXPECT_EQ(test::parseNameOrDie("attr/bar"),
+ styleable->entries[0].name.value());
+ EXPECT_EQ(test::parseNameOrDie("attr/bat"),
+ styleable->entries[1].name.value());
}
TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) {
- std::string input = "<declare-styleable name=\"foo\" xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n"
- " <attr name=\"*android:bar\" />\n"
- " <attr name=\"privAndroid:bat\" />\n"
- "</declare-styleable>";
- ASSERT_TRUE(testParse(input));
- Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo");
- ASSERT_NE(nullptr, styleable);
- ASSERT_EQ(2u, styleable->entries.size());
+ std::string input =
+ "<declare-styleable name=\"foo\" "
+ "xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n"
+ " <attr name=\"*android:bar\" />\n"
+ " <attr name=\"privAndroid:bat\" />\n"
+ "</declare-styleable>";
+ ASSERT_TRUE(testParse(input));
+ Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo");
+ ASSERT_NE(nullptr, styleable);
+ ASSERT_EQ(2u, styleable->entries.size());
- EXPECT_TRUE(styleable->entries[0].privateReference);
- AAPT_ASSERT_TRUE(styleable->entries[0].name);
- EXPECT_EQ(std::string("android"), styleable->entries[0].name.value().package);
+ EXPECT_TRUE(styleable->entries[0].privateReference);
+ AAPT_ASSERT_TRUE(styleable->entries[0].name);
+ EXPECT_EQ(std::string("android"), styleable->entries[0].name.value().package);
- EXPECT_TRUE(styleable->entries[1].privateReference);
- AAPT_ASSERT_TRUE(styleable->entries[1].name);
- EXPECT_EQ(std::string("android"), styleable->entries[1].name.value().package);
+ EXPECT_TRUE(styleable->entries[1].privateReference);
+ AAPT_ASSERT_TRUE(styleable->entries[1].name);
+ EXPECT_EQ(std::string("android"), styleable->entries[1].name.value().package);
}
TEST_F(ResourceParserTest, ParseArray) {
- std::string input = "<array name=\"foo\">\n"
- " <item>@string/ref</item>\n"
- " <item>hey</item>\n"
- " <item>23</item>\n"
- "</array>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<array name=\"foo\">\n"
+ " <item>@string/ref</item>\n"
+ " <item>hey</item>\n"
+ " <item>23</item>\n"
+ "</array>";
+ ASSERT_TRUE(testParse(input));
- Array* array = test::getValue<Array>(&mTable, "array/foo");
- ASSERT_NE(array, nullptr);
- ASSERT_EQ(3u, array->items.size());
+ Array* array = test::getValue<Array>(&mTable, "array/foo");
+ ASSERT_NE(array, nullptr);
+ ASSERT_EQ(3u, array->items.size());
- EXPECT_NE(nullptr, valueCast<Reference>(array->items[0].get()));
- EXPECT_NE(nullptr, valueCast<String>(array->items[1].get()));
- EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get()));
+ EXPECT_NE(nullptr, valueCast<Reference>(array->items[0].get()));
+ EXPECT_NE(nullptr, valueCast<String>(array->items[1].get()));
+ EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get()));
}
TEST_F(ResourceParserTest, ParseStringArray) {
- std::string input = "<string-array name=\"foo\">\n"
- " <item>\"Werk\"</item>\n"
- "</string-array>\n";
- ASSERT_TRUE(testParse(input));
- EXPECT_NE(nullptr, test::getValue<Array>(&mTable, "array/foo"));
+ std::string input =
+ "<string-array name=\"foo\">\n"
+ " <item>\"Werk\"</item>\n"
+ "</string-array>\n";
+ ASSERT_TRUE(testParse(input));
+ EXPECT_NE(nullptr, test::getValue<Array>(&mTable, "array/foo"));
}
TEST_F(ResourceParserTest, ParsePlural) {
- std::string input = "<plurals name=\"foo\">\n"
- " <item quantity=\"other\">apples</item>\n"
- " <item quantity=\"one\">apple</item>\n"
- "</plurals>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<plurals name=\"foo\">\n"
+ " <item quantity=\"other\">apples</item>\n"
+ " <item quantity=\"one\">apple</item>\n"
+ "</plurals>";
+ ASSERT_TRUE(testParse(input));
}
TEST_F(ResourceParserTest, ParseCommentsWithResource) {
- std::string input = "<!--This is a comment-->\n"
- "<string name=\"foo\">Hi</string>";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ "<!--This is a comment-->\n"
+ "<string name=\"foo\">Hi</string>";
+ ASSERT_TRUE(testParse(input));
- String* value = test::getValue<String>(&mTable, "string/foo");
- ASSERT_NE(nullptr, value);
- EXPECT_EQ(value->getComment(), "This is a comment");
+ String* value = test::getValue<String>(&mTable, "string/foo");
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(value->getComment(), "This is a comment");
}
TEST_F(ResourceParserTest, DoNotCombineMultipleComments) {
- std::string input = "<!--One-->\n"
- "<!--Two-->\n"
- "<string name=\"foo\">Hi</string>";
+ std::string input =
+ "<!--One-->\n"
+ "<!--Two-->\n"
+ "<string name=\"foo\">Hi</string>";
- ASSERT_TRUE(testParse(input));
+ ASSERT_TRUE(testParse(input));
- String* value = test::getValue<String>(&mTable, "string/foo");
- ASSERT_NE(nullptr, value);
- EXPECT_EQ(value->getComment(), "Two");
+ String* value = test::getValue<String>(&mTable, "string/foo");
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(value->getComment(), "Two");
}
TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) {
- std::string input = "<!--One-->\n"
- "<string name=\"foo\">\n"
- " Hi\n"
- "<!--Two-->\n"
- "</string>";
+ std::string input =
+ "<!--One-->\n"
+ "<string name=\"foo\">\n"
+ " Hi\n"
+ "<!--Two-->\n"
+ "</string>";
- ASSERT_TRUE(testParse(input));
+ ASSERT_TRUE(testParse(input));
- String* value = test::getValue<String>(&mTable, "string/foo");
- ASSERT_NE(nullptr, value);
- EXPECT_EQ(value->getComment(), "One");
+ String* value = test::getValue<String>(&mTable, "string/foo");
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(value->getComment(), "One");
}
TEST_F(ResourceParserTest, ParseNestedComments) {
- // We only care about declare-styleable and enum/flag attributes because comments
- // from those end up in R.java
- std::string input = R"EOF(
+ // We only care about declare-styleable and enum/flag attributes because
+ // comments
+ // from those end up in R.java
+ std::string input = R"EOF(
<declare-styleable name="foo">
<!-- The name of the bar -->
<attr name="barName" format="string|reference" />
@@ -541,19 +588,21 @@
<!-- The very first -->
<enum name="one" value="1" />
</attr>)EOF";
- ASSERT_TRUE(testParse(input));
+ ASSERT_TRUE(testParse(input));
- Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo");
- ASSERT_NE(nullptr, styleable);
- ASSERT_EQ(1u, styleable->entries.size());
+ Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo");
+ ASSERT_NE(nullptr, styleable);
+ ASSERT_EQ(1u, styleable->entries.size());
- EXPECT_EQ(StringPiece("The name of the bar"), styleable->entries.front().getComment());
+ EXPECT_EQ(StringPiece("The name of the bar"),
+ styleable->entries.front().getComment());
- Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
- ASSERT_NE(nullptr, attr);
- ASSERT_EQ(1u, attr->symbols.size());
+ Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
+ ASSERT_NE(nullptr, attr);
+ ASSERT_EQ(1u, attr->symbols.size());
- EXPECT_EQ(StringPiece("The very first"), attr->symbols.front().symbol.getComment());
+ EXPECT_EQ(StringPiece("The very first"),
+ attr->symbols.front().symbol.getComment());
}
/*
@@ -561,15 +610,15 @@
* (as an ID has no value).
*/
TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) {
- std::string input = "<public type=\"id\" name=\"foo\"/>";
- ASSERT_TRUE(testParse(input));
+ std::string input = "<public type=\"id\" name=\"foo\"/>";
+ ASSERT_TRUE(testParse(input));
- Id* id = test::getValue<Id>(&mTable, "id/foo");
- ASSERT_NE(nullptr, id);
+ Id* id = test::getValue<Id>(&mTable, "id/foo");
+ ASSERT_NE(nullptr, id);
}
TEST_F(ResourceParserTest, KeepAllProducts) {
- std::string input = R"EOF(
+ std::string input = R"EOF(
<string name="foo" product="phone">hi</string>
<string name="foo" product="no-sdcard">ho</string>
<string name="bar" product="">wee</string>
@@ -577,88 +626,92 @@
<string name="bit" product="phablet">hoot</string>
<string name="bot" product="default">yes</string>
)EOF";
- ASSERT_TRUE(testParse(input));
+ ASSERT_TRUE(testParse(input));
- EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/foo",
- ConfigDescription::defaultConfig(),
- "phone"));
- EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/foo",
- ConfigDescription::defaultConfig(),
- "no-sdcard"));
- EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/bar",
- ConfigDescription::defaultConfig(),
- ""));
- EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/baz",
- ConfigDescription::defaultConfig(),
- ""));
- EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/bit",
- ConfigDescription::defaultConfig(),
- "phablet"));
- EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/bot",
- ConfigDescription::defaultConfig(),
- "default"));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(
+ &mTable, "string/foo",
+ ConfigDescription::defaultConfig(), "phone"));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(
+ &mTable, "string/foo",
+ ConfigDescription::defaultConfig(), "no-sdcard"));
+ EXPECT_NE(nullptr,
+ test::getValueForConfigAndProduct<String>(
+ &mTable, "string/bar", ConfigDescription::defaultConfig(), ""));
+ EXPECT_NE(nullptr,
+ test::getValueForConfigAndProduct<String>(
+ &mTable, "string/baz", ConfigDescription::defaultConfig(), ""));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(
+ &mTable, "string/bit",
+ ConfigDescription::defaultConfig(), "phablet"));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(
+ &mTable, "string/bot",
+ ConfigDescription::defaultConfig(), "default"));
}
TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) {
- std::string input = R"EOF(
+ std::string input = R"EOF(
<public-group type="attr" first-id="0x01010040">
<public name="foo" />
<public name="bar" />
</public-group>)EOF";
- ASSERT_TRUE(testParse(input));
+ ASSERT_TRUE(testParse(input));
- Maybe<ResourceTable::SearchResult> result = mTable.findResource(
- test::parseNameOrDie("attr/foo"));
- AAPT_ASSERT_TRUE(result);
+ Maybe<ResourceTable::SearchResult> result =
+ mTable.findResource(test::parseNameOrDie("attr/foo"));
+ AAPT_ASSERT_TRUE(result);
- AAPT_ASSERT_TRUE(result.value().package->id);
- AAPT_ASSERT_TRUE(result.value().type->id);
- AAPT_ASSERT_TRUE(result.value().entry->id);
- ResourceId actualId(result.value().package->id.value(),
+ AAPT_ASSERT_TRUE(result.value().package->id);
+ AAPT_ASSERT_TRUE(result.value().type->id);
+ AAPT_ASSERT_TRUE(result.value().entry->id);
+ ResourceId actualId(result.value().package->id.value(),
+ result.value().type->id.value(),
+ result.value().entry->id.value());
+ EXPECT_EQ(ResourceId(0x01010040), actualId);
+
+ result = mTable.findResource(test::parseNameOrDie("attr/bar"));
+ AAPT_ASSERT_TRUE(result);
+
+ AAPT_ASSERT_TRUE(result.value().package->id);
+ AAPT_ASSERT_TRUE(result.value().type->id);
+ AAPT_ASSERT_TRUE(result.value().entry->id);
+ actualId = ResourceId(result.value().package->id.value(),
result.value().type->id.value(),
result.value().entry->id.value());
- EXPECT_EQ(ResourceId(0x01010040), actualId);
-
- result = mTable.findResource(test::parseNameOrDie("attr/bar"));
- AAPT_ASSERT_TRUE(result);
-
- AAPT_ASSERT_TRUE(result.value().package->id);
- AAPT_ASSERT_TRUE(result.value().type->id);
- AAPT_ASSERT_TRUE(result.value().entry->id);
- actualId = ResourceId(result.value().package->id.value(),
- result.value().type->id.value(),
- result.value().entry->id.value());
- EXPECT_EQ(ResourceId(0x01010041), actualId);
+ EXPECT_EQ(ResourceId(0x01010041), actualId);
}
TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) {
- std::string input = R"EOF(<item type="layout" name="foo">@layout/bar</item>)EOF";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ R"EOF(<item type="layout" name="foo">@layout/bar</item>)EOF";
+ ASSERT_TRUE(testParse(input));
- input = R"EOF(<item type="layout" name="bar">"this is a string"</item>)EOF";
- ASSERT_FALSE(testParse(input));
+ input = R"EOF(<item type="layout" name="bar">"this is a string"</item>)EOF";
+ ASSERT_FALSE(testParse(input));
}
-TEST_F(ResourceParserTest, AddResourcesElementShouldAddEntryWithUndefinedSymbol) {
- std::string input = R"EOF(<add-resource name="bar" type="string" />)EOF";
- ASSERT_TRUE(testParse(input));
+TEST_F(ResourceParserTest,
+ AddResourcesElementShouldAddEntryWithUndefinedSymbol) {
+ std::string input = R"EOF(<add-resource name="bar" type="string" />)EOF";
+ ASSERT_TRUE(testParse(input));
- Maybe<ResourceTable::SearchResult> result = mTable.findResource(
- test::parseNameOrDie("string/bar"));
- AAPT_ASSERT_TRUE(result);
- const ResourceEntry* entry = result.value().entry;
- ASSERT_NE(nullptr, entry);
- EXPECT_EQ(SymbolState::kUndefined, entry->symbolStatus.state);
+ Maybe<ResourceTable::SearchResult> result =
+ mTable.findResource(test::parseNameOrDie("string/bar"));
+ AAPT_ASSERT_TRUE(result);
+ const ResourceEntry* entry = result.value().entry;
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(SymbolState::kUndefined, entry->symbolStatus.state);
}
TEST_F(ResourceParserTest, ParseItemElementWithFormat) {
- std::string input = R"EOF(<item name="foo" type="integer" format="float">0.3</item>)EOF";
- ASSERT_TRUE(testParse(input));
+ std::string input =
+ R"EOF(<item name="foo" type="integer" format="float">0.3</item>)EOF";
+ ASSERT_TRUE(testParse(input));
- BinaryPrimitive* val = test::getValue<BinaryPrimitive>(&mTable, "integer/foo");
- ASSERT_NE(nullptr, val);
+ BinaryPrimitive* val =
+ test::getValue<BinaryPrimitive>(&mTable, "integer/foo");
+ ASSERT_NE(nullptr, val);
- EXPECT_EQ(uint32_t(android::Res_value::TYPE_FLOAT), val->value.dataType);
+ EXPECT_EQ(uint32_t(android::Res_value::TYPE_FLOAT), val->value.dataType);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index bdc6a8c..c52c91c 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -14,266 +14,282 @@
* limitations under the License.
*/
+#include "ResourceTable.h"
#include "ConfigDescription.h"
#include "NameMangler.h"
-#include "ResourceTable.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
#include "util/Util.h"
-#include <algorithm>
#include <androidfw/ResourceTypes.h>
+#include <algorithm>
#include <memory>
#include <string>
#include <tuple>
namespace aapt {
-static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
- return lhs->type < rhs;
+static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs,
+ ResourceType rhs) {
+ return lhs->type < rhs;
}
template <typename T>
static bool lessThanStructWithName(const std::unique_ptr<T>& lhs,
const StringPiece& rhs) {
- return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
+ return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
}
ResourceTablePackage* ResourceTable::findPackage(const StringPiece& name) {
- const auto last = packages.end();
- auto iter = std::lower_bound(packages.begin(), last, name,
- lessThanStructWithName<ResourceTablePackage>);
- if (iter != last && name == (*iter)->name) {
- return iter->get();
- }
- return nullptr;
+ const auto last = packages.end();
+ auto iter = std::lower_bound(packages.begin(), last, name,
+ lessThanStructWithName<ResourceTablePackage>);
+ if (iter != last && name == (*iter)->name) {
+ return iter->get();
+ }
+ return nullptr;
}
ResourceTablePackage* ResourceTable::findPackageById(uint8_t id) {
- for (auto& package : packages) {
- if (package->id && package->id.value() == id) {
- return package.get();
- }
+ for (auto& package : packages) {
+ if (package->id && package->id.value() == id) {
+ return package.get();
}
- return nullptr;
+ }
+ return nullptr;
}
-ResourceTablePackage* ResourceTable::createPackage(const StringPiece& name, Maybe<uint8_t> id) {
- ResourceTablePackage* package = findOrCreatePackage(name);
- if (id && !package->id) {
- package->id = id;
- return package;
- }
-
- if (id && package->id && package->id.value() != id.value()) {
- return nullptr;
- }
+ResourceTablePackage* ResourceTable::createPackage(const StringPiece& name,
+ Maybe<uint8_t> id) {
+ ResourceTablePackage* package = findOrCreatePackage(name);
+ if (id && !package->id) {
+ package->id = id;
return package;
+ }
+
+ if (id && package->id && package->id.value() != id.value()) {
+ return nullptr;
+ }
+ return package;
}
-ResourceTablePackage* ResourceTable::findOrCreatePackage(const StringPiece& name) {
- const auto last = packages.end();
- auto iter = std::lower_bound(packages.begin(), last, name,
- lessThanStructWithName<ResourceTablePackage>);
- if (iter != last && name == (*iter)->name) {
- return iter->get();
- }
+ResourceTablePackage* ResourceTable::findOrCreatePackage(
+ const StringPiece& name) {
+ const auto last = packages.end();
+ auto iter = std::lower_bound(packages.begin(), last, name,
+ lessThanStructWithName<ResourceTablePackage>);
+ if (iter != last && name == (*iter)->name) {
+ return iter->get();
+ }
- std::unique_ptr<ResourceTablePackage> newPackage = util::make_unique<ResourceTablePackage>();
- newPackage->name = name.toString();
- return packages.emplace(iter, std::move(newPackage))->get();
+ std::unique_ptr<ResourceTablePackage> newPackage =
+ util::make_unique<ResourceTablePackage>();
+ newPackage->name = name.toString();
+ return packages.emplace(iter, std::move(newPackage))->get();
}
ResourceTableType* ResourceTablePackage::findType(ResourceType type) {
- const auto last = types.end();
- auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
- if (iter != last && (*iter)->type == type) {
- return iter->get();
- }
- return nullptr;
+ const auto last = types.end();
+ auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
+ if (iter != last && (*iter)->type == type) {
+ return iter->get();
+ }
+ return nullptr;
}
ResourceTableType* ResourceTablePackage::findOrCreateType(ResourceType type) {
- const auto last = types.end();
- auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
- if (iter != last && (*iter)->type == type) {
- return iter->get();
- }
- return types.emplace(iter, new ResourceTableType(type))->get();
+ const auto last = types.end();
+ auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
+ if (iter != last && (*iter)->type == type) {
+ return iter->get();
+ }
+ return types.emplace(iter, new ResourceTableType(type))->get();
}
ResourceEntry* ResourceTableType::findEntry(const StringPiece& name) {
- const auto last = entries.end();
- auto iter = std::lower_bound(entries.begin(), last, name,
- lessThanStructWithName<ResourceEntry>);
- if (iter != last && name == (*iter)->name) {
- return iter->get();
- }
- return nullptr;
+ const auto last = entries.end();
+ auto iter = std::lower_bound(entries.begin(), last, name,
+ lessThanStructWithName<ResourceEntry>);
+ if (iter != last && name == (*iter)->name) {
+ return iter->get();
+ }
+ return nullptr;
}
ResourceEntry* ResourceTableType::findOrCreateEntry(const StringPiece& name) {
- auto last = entries.end();
- auto iter = std::lower_bound(entries.begin(), last, name,
- lessThanStructWithName<ResourceEntry>);
- if (iter != last && name == (*iter)->name) {
- return iter->get();
- }
- return entries.emplace(iter, new ResourceEntry(name))->get();
+ auto last = entries.end();
+ auto iter = std::lower_bound(entries.begin(), last, name,
+ lessThanStructWithName<ResourceEntry>);
+ if (iter != last && name == (*iter)->name) {
+ return iter->get();
+ }
+ return entries.emplace(iter, new ResourceEntry(name))->get();
}
ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config) {
- return findValue(config, StringPiece());
+ return findValue(config, StringPiece());
}
struct ConfigKey {
- const ConfigDescription* config;
- const StringPiece& product;
+ const ConfigDescription* config;
+ const StringPiece& product;
};
-bool ltConfigKeyRef(const std::unique_ptr<ResourceConfigValue>& lhs, const ConfigKey& rhs) {
- int cmp = lhs->config.compare(*rhs.config);
- if (cmp == 0) {
- cmp = StringPiece(lhs->product).compare(rhs.product);
- }
- return cmp < 0;
+bool ltConfigKeyRef(const std::unique_ptr<ResourceConfigValue>& lhs,
+ const ConfigKey& rhs) {
+ int cmp = lhs->config.compare(*rhs.config);
+ if (cmp == 0) {
+ cmp = StringPiece(lhs->product).compare(rhs.product);
+ }
+ return cmp < 0;
}
ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config,
const StringPiece& product) {
- auto iter = std::lower_bound(values.begin(), values.end(),
- ConfigKey{ &config, product }, ltConfigKeyRef);
- if (iter != values.end()) {
- ResourceConfigValue* value = iter->get();
- if (value->config == config && StringPiece(value->product) == product) {
- return value;
- }
+ auto iter = std::lower_bound(values.begin(), values.end(),
+ ConfigKey{&config, product}, ltConfigKeyRef);
+ if (iter != values.end()) {
+ ResourceConfigValue* value = iter->get();
+ if (value->config == config && StringPiece(value->product) == product) {
+ return value;
}
- return nullptr;
+ }
+ return nullptr;
}
-ResourceConfigValue* ResourceEntry::findOrCreateValue(const ConfigDescription& config,
- const StringPiece& product) {
- auto iter = std::lower_bound(values.begin(), values.end(),
- ConfigKey{ &config, product }, ltConfigKeyRef);
- if (iter != values.end()) {
- ResourceConfigValue* value = iter->get();
- if (value->config == config && StringPiece(value->product) == product) {
- return value;
- }
+ResourceConfigValue* ResourceEntry::findOrCreateValue(
+ const ConfigDescription& config, const StringPiece& product) {
+ auto iter = std::lower_bound(values.begin(), values.end(),
+ ConfigKey{&config, product}, ltConfigKeyRef);
+ if (iter != values.end()) {
+ ResourceConfigValue* value = iter->get();
+ if (value->config == config && StringPiece(value->product) == product) {
+ return value;
}
- ResourceConfigValue* newValue = values.insert(
- iter, util::make_unique<ResourceConfigValue>(config, product))->get();
- return newValue;
+ }
+ ResourceConfigValue* newValue =
+ values
+ .insert(iter, util::make_unique<ResourceConfigValue>(config, product))
+ ->get();
+ return newValue;
}
-std::vector<ResourceConfigValue*> ResourceEntry::findAllValues(const ConfigDescription& config) {
- std::vector<ResourceConfigValue*> results;
+std::vector<ResourceConfigValue*> ResourceEntry::findAllValues(
+ const ConfigDescription& config) {
+ std::vector<ResourceConfigValue*> results;
- auto iter = values.begin();
- for (; iter != values.end(); ++iter) {
- ResourceConfigValue* value = iter->get();
- if (value->config == config) {
- results.push_back(value);
- ++iter;
- break;
- }
+ auto iter = values.begin();
+ for (; iter != values.end(); ++iter) {
+ ResourceConfigValue* value = iter->get();
+ if (value->config == config) {
+ results.push_back(value);
+ ++iter;
+ break;
}
+ }
- for (; iter != values.end(); ++iter) {
- ResourceConfigValue* value = iter->get();
- if (value->config == config) {
- results.push_back(value);
- }
+ for (; iter != values.end(); ++iter) {
+ ResourceConfigValue* value = iter->get();
+ if (value->config == config) {
+ results.push_back(value);
}
- return results;
+ }
+ return results;
}
std::vector<ResourceConfigValue*> ResourceEntry::findValuesIf(
- const std::function<bool(ResourceConfigValue*)>& f) {
- std::vector<ResourceConfigValue*> results;
- for (auto& configValue : values) {
- if (f(configValue.get())) {
- results.push_back(configValue.get());
- }
+ const std::function<bool(ResourceConfigValue*)>& f) {
+ std::vector<ResourceConfigValue*> results;
+ for (auto& configValue : values) {
+ if (f(configValue.get())) {
+ results.push_back(configValue.get());
}
- return results;
+ }
+ return results;
}
/**
* The default handler for collisions.
*
- * Typically, a weak value will be overridden by a strong value. An existing weak
+ * Typically, a weak value will be overridden by a strong value. An existing
+ * weak
* value will not be overridden by an incoming weak value.
*
* There are some exceptions:
*
* Attributes: There are two types of Attribute values: USE and DECL.
*
- * USE is anywhere an Attribute is declared without a format, and in a place that would
+ * USE is anywhere an Attribute is declared without a format, and in a place
+ * that would
* be legal to declare if the Attribute already existed. This is typically in a
- * <declare-styleable> tag. Attributes defined in a <declare-styleable> are also weak.
+ * <declare-styleable> tag. Attributes defined in a <declare-styleable> are also
+ * weak.
*
- * DECL is an absolute declaration of an Attribute and specifies an explicit format.
+ * DECL is an absolute declaration of an Attribute and specifies an explicit
+ * format.
*
- * A DECL will override a USE without error. Two DECLs must match in their format for there to be
+ * A DECL will override a USE without error. Two DECLs must match in their
+ * format for there to be
* no error.
*/
ResourceTable::CollisionResult ResourceTable::resolveValueCollision(
- Value* existing, Value* incoming) {
- Attribute* existingAttr = valueCast<Attribute>(existing);
- Attribute* incomingAttr = valueCast<Attribute>(incoming);
- if (!incomingAttr) {
- if (incoming->isWeak()) {
- // We're trying to add a weak resource but a resource
- // already exists. Keep the existing.
- return CollisionResult::kKeepOriginal;
- } else if (existing->isWeak()) {
- // Override the weak resource with the new strong resource.
- return CollisionResult::kTakeNew;
- }
- // The existing and incoming values are strong, this is an error
- // if the values are not both attributes.
- return CollisionResult::kConflict;
+ Value* existing, Value* incoming) {
+ Attribute* existingAttr = valueCast<Attribute>(existing);
+ Attribute* incomingAttr = valueCast<Attribute>(incoming);
+ if (!incomingAttr) {
+ if (incoming->isWeak()) {
+ // We're trying to add a weak resource but a resource
+ // already exists. Keep the existing.
+ return CollisionResult::kKeepOriginal;
+ } else if (existing->isWeak()) {
+ // Override the weak resource with the new strong resource.
+ return CollisionResult::kTakeNew;
}
-
- if (!existingAttr) {
- if (existing->isWeak()) {
- // The existing value is not an attribute and it is weak,
- // so take the incoming attribute value.
- return CollisionResult::kTakeNew;
- }
- // The existing value is not an attribute and it is strong,
- // so the incoming attribute value is an error.
- return CollisionResult::kConflict;
- }
-
- assert(incomingAttr && existingAttr);
-
- //
- // Attribute specific handling. At this point we know both
- // values are attributes. Since we can declare and define
- // attributes all-over, we do special handling to see
- // which definition sticks.
- //
- if (existingAttr->typeMask == incomingAttr->typeMask) {
- // The two attributes are both DECLs, but they are plain attributes
- // with the same formats.
- // Keep the strongest one.
- return existingAttr->isWeak() ? CollisionResult::kTakeNew : CollisionResult::kKeepOriginal;
- }
-
- if (existingAttr->isWeak() && existingAttr->typeMask == android::ResTable_map::TYPE_ANY) {
- // Any incoming attribute is better than this.
- return CollisionResult::kTakeNew;
- }
-
- if (incomingAttr->isWeak() && incomingAttr->typeMask == android::ResTable_map::TYPE_ANY) {
- // The incoming attribute may be a USE instead of a DECL.
- // Keep the existing attribute.
- return CollisionResult::kKeepOriginal;
- }
+ // The existing and incoming values are strong, this is an error
+ // if the values are not both attributes.
return CollisionResult::kConflict;
+ }
+
+ if (!existingAttr) {
+ if (existing->isWeak()) {
+ // The existing value is not an attribute and it is weak,
+ // so take the incoming attribute value.
+ return CollisionResult::kTakeNew;
+ }
+ // The existing value is not an attribute and it is strong,
+ // so the incoming attribute value is an error.
+ return CollisionResult::kConflict;
+ }
+
+ assert(incomingAttr && existingAttr);
+
+ //
+ // Attribute specific handling. At this point we know both
+ // values are attributes. Since we can declare and define
+ // attributes all-over, we do special handling to see
+ // which definition sticks.
+ //
+ if (existingAttr->typeMask == incomingAttr->typeMask) {
+ // The two attributes are both DECLs, but they are plain attributes
+ // with the same formats.
+ // Keep the strongest one.
+ return existingAttr->isWeak() ? CollisionResult::kTakeNew
+ : CollisionResult::kKeepOriginal;
+ }
+
+ if (existingAttr->isWeak() &&
+ existingAttr->typeMask == android::ResTable_map::TYPE_ANY) {
+ // Any incoming attribute is better than this.
+ return CollisionResult::kTakeNew;
+ }
+
+ if (incomingAttr->isWeak() &&
+ incomingAttr->typeMask == android::ResTable_map::TYPE_ANY) {
+ // The incoming attribute may be a USE instead of a DECL.
+ // Keep the existing attribute.
+ return CollisionResult::kKeepOriginal;
+ }
+ return CollisionResult::kConflict;
}
static constexpr const char* kValidNameChars = "._-";
@@ -284,8 +300,8 @@
const StringPiece& product,
std::unique_ptr<Value> value,
IDiagnostics* diag) {
- return addResourceImpl(name, {}, config, product, std::move(value), kValidNameChars,
- resolveValueCollision, diag);
+ return addResourceImpl(name, {}, config, product, std::move(value),
+ kValidNameChars, resolveValueCollision, diag);
}
bool ResourceTable::addResource(const ResourceNameRef& name,
@@ -294,8 +310,8 @@
const StringPiece& product,
std::unique_ptr<Value> value,
IDiagnostics* diag) {
- return addResourceImpl(name, resId, config, product, std::move(value), kValidNameChars,
- resolveValueCollision, diag);
+ return addResourceImpl(name, resId, config, product, std::move(value),
+ kValidNameChars, resolveValueCollision, diag);
}
bool ResourceTable::addFileReference(const ResourceNameRef& name,
@@ -303,31 +319,29 @@
const Source& source,
const StringPiece& path,
IDiagnostics* diag) {
- return addFileReferenceImpl(name, config, source, path, nullptr, kValidNameChars, diag);
+ return addFileReferenceImpl(name, config, source, path, nullptr,
+ kValidNameChars, diag);
}
-bool ResourceTable::addFileReferenceAllowMangled(const ResourceNameRef& name,
- const ConfigDescription& config,
- const Source& source,
- const StringPiece& path,
- io::IFile* file,
- IDiagnostics* diag) {
- return addFileReferenceImpl(name, config, source, path, file, kValidNameMangledChars, diag);
+bool ResourceTable::addFileReferenceAllowMangled(
+ const ResourceNameRef& name, const ConfigDescription& config,
+ const Source& source, const StringPiece& path, io::IFile* file,
+ IDiagnostics* diag) {
+ return addFileReferenceImpl(name, config, source, path, file,
+ kValidNameMangledChars, diag);
}
-bool ResourceTable::addFileReferenceImpl(const ResourceNameRef& name,
- const ConfigDescription& config,
- const Source& source,
- const StringPiece& path,
- io::IFile* file,
- const char* validChars,
- IDiagnostics* diag) {
- std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
- stringPool.makeRef(path));
- fileRef->setSource(source);
- fileRef->file = file;
- return addResourceImpl(name, ResourceId{}, config, StringPiece{}, std::move(fileRef),
- validChars, resolveValueCollision, diag);
+bool ResourceTable::addFileReferenceImpl(
+ const ResourceNameRef& name, const ConfigDescription& config,
+ const Source& source, const StringPiece& path, io::IFile* file,
+ const char* validChars, IDiagnostics* diag) {
+ std::unique_ptr<FileReference> fileRef =
+ util::make_unique<FileReference>(stringPool.makeRef(path));
+ fileRef->setSource(source);
+ fileRef->file = file;
+ return addResourceImpl(name, ResourceId{}, config, StringPiece{},
+ std::move(fileRef), validChars, resolveValueCollision,
+ diag);
}
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
@@ -335,8 +349,8 @@
const StringPiece& product,
std::unique_ptr<Value> value,
IDiagnostics* diag) {
- return addResourceImpl(name, ResourceId{}, config, product, std::move(value),
- kValidNameMangledChars, resolveValueCollision, diag);
+ return addResourceImpl(name, ResourceId{}, config, product, std::move(value),
+ kValidNameMangledChars, resolveValueCollision, diag);
}
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
@@ -345,220 +359,193 @@
const StringPiece& product,
std::unique_ptr<Value> value,
IDiagnostics* diag) {
- return addResourceImpl(name, id, config, product, std::move(value), kValidNameMangledChars,
- resolveValueCollision, diag);
+ return addResourceImpl(name, id, config, product, std::move(value),
+ kValidNameMangledChars, resolveValueCollision, diag);
}
-bool ResourceTable::addResourceImpl(const ResourceNameRef& name,
- const ResourceId& resId,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value,
- const char* validChars,
- const CollisionResolverFunc& conflictResolver,
- IDiagnostics* diag) {
- assert(value && "value can't be nullptr");
- assert(diag && "diagnostics can't be nullptr");
+bool ResourceTable::addResourceImpl(
+ const ResourceNameRef& name, const ResourceId& resId,
+ const ConfigDescription& config, const StringPiece& product,
+ std::unique_ptr<Value> value, const char* validChars,
+ const CollisionResolverFunc& conflictResolver, IDiagnostics* diag) {
+ assert(value && "value can't be nullptr");
+ assert(diag && "diagnostics can't be nullptr");
- auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
- if (badCharIter != name.entry.end()) {
- diag->error(DiagMessage(value->getSource())
- << "resource '"
- << name
- << "' has invalid entry name '"
- << name.entry
- << "'. Invalid character '"
- << StringPiece(badCharIter, 1)
- << "'");
- return false;
- }
+ auto badCharIter =
+ util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
+ if (badCharIter != name.entry.end()) {
+ diag->error(DiagMessage(value->getSource())
+ << "resource '" << name << "' has invalid entry name '"
+ << name.entry << "'. Invalid character '"
+ << StringPiece(badCharIter, 1) << "'");
+ return false;
+ }
- ResourceTablePackage* package = findOrCreatePackage(name.package);
- if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
- diag->error(DiagMessage(value->getSource())
- << "trying to add resource '"
- << name
- << "' with ID "
- << resId
- << " but package '"
- << package->name
- << "' already has ID "
- << std::hex << (int) package->id.value() << std::dec);
- return false;
- }
+ ResourceTablePackage* package = findOrCreatePackage(name.package);
+ if (resId.isValid() && package->id &&
+ package->id.value() != resId.packageId()) {
+ diag->error(DiagMessage(value->getSource())
+ << "trying to add resource '" << name << "' with ID " << resId
+ << " but package '" << package->name << "' already has ID "
+ << std::hex << (int)package->id.value() << std::dec);
+ return false;
+ }
- ResourceTableType* type = package->findOrCreateType(name.type);
- if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
- diag->error(DiagMessage(value->getSource())
- << "trying to add resource '"
- << name
- << "' with ID "
- << resId
- << " but type '"
- << type->type
- << "' already has ID "
- << std::hex << (int) type->id.value() << std::dec);
- return false;
- }
+ ResourceTableType* type = package->findOrCreateType(name.type);
+ if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
+ diag->error(DiagMessage(value->getSource())
+ << "trying to add resource '" << name << "' with ID " << resId
+ << " but type '" << type->type << "' already has ID "
+ << std::hex << (int)type->id.value() << std::dec);
+ return false;
+ }
- ResourceEntry* entry = type->findOrCreateEntry(name.entry);
- if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
- diag->error(DiagMessage(value->getSource())
- << "trying to add resource '"
- << name
- << "' with ID "
- << resId
- << " but resource already has ID "
- << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
- return false;
- }
+ ResourceEntry* entry = type->findOrCreateEntry(name.entry);
+ if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
+ diag->error(DiagMessage(value->getSource())
+ << "trying to add resource '" << name << "' with ID " << resId
+ << " but resource already has ID "
+ << ResourceId(package->id.value(), type->id.value(),
+ entry->id.value()));
+ return false;
+ }
- ResourceConfigValue* configValue = entry->findOrCreateValue(config, product);
- if (!configValue->value) {
- // Resource does not exist, add it now.
+ ResourceConfigValue* configValue = entry->findOrCreateValue(config, product);
+ if (!configValue->value) {
+ // Resource does not exist, add it now.
+ configValue->value = std::move(value);
+
+ } else {
+ switch (conflictResolver(configValue->value.get(), value.get())) {
+ case CollisionResult::kTakeNew:
+ // Take the incoming value.
configValue->value = std::move(value);
+ break;
- } else {
- switch (conflictResolver(configValue->value.get(), value.get())) {
- case CollisionResult::kTakeNew:
- // Take the incoming value.
- configValue->value = std::move(value);
- break;
+ case CollisionResult::kConflict:
+ diag->error(DiagMessage(value->getSource())
+ << "duplicate value for resource '" << name << "' "
+ << "with config '" << config << "'");
+ diag->error(DiagMessage(configValue->value->getSource())
+ << "resource previously defined here");
+ return false;
- case CollisionResult::kConflict:
- diag->error(DiagMessage(value->getSource())
- << "duplicate value for resource '" << name << "' "
- << "with config '" << config << "'");
- diag->error(DiagMessage(configValue->value->getSource())
- << "resource previously defined here");
- return false;
-
- case CollisionResult::kKeepOriginal:
- break;
- }
+ case CollisionResult::kKeepOriginal:
+ break;
}
+ }
- if (resId.isValid()) {
- package->id = resId.packageId();
- type->id = resId.typeId();
- entry->id = resId.entryId();
- }
- return true;
+ if (resId.isValid()) {
+ package->id = resId.packageId();
+ type->id = resId.typeId();
+ entry->id = resId.entryId();
+ }
+ return true;
}
-bool ResourceTable::setSymbolState(const ResourceNameRef& name, const ResourceId& resId,
+bool ResourceTable::setSymbolState(const ResourceNameRef& name,
+ const ResourceId& resId,
const Symbol& symbol, IDiagnostics* diag) {
- return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag);
+ return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag);
}
bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name,
const ResourceId& resId,
- const Symbol& symbol, IDiagnostics* diag) {
- return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag);
+ const Symbol& symbol,
+ IDiagnostics* diag) {
+ return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag);
}
-bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const ResourceId& resId,
- const Symbol& symbol, const char* validChars,
+bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name,
+ const ResourceId& resId,
+ const Symbol& symbol,
+ const char* validChars,
IDiagnostics* diag) {
- assert(diag && "diagnostics can't be nullptr");
+ assert(diag && "diagnostics can't be nullptr");
- auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
- if (badCharIter != name.entry.end()) {
- diag->error(DiagMessage(symbol.source)
- << "resource '"
- << name
- << "' has invalid entry name '"
- << name.entry
- << "'. Invalid character '"
- << StringPiece(badCharIter, 1)
- << "'");
- return false;
- }
+ auto badCharIter =
+ util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
+ if (badCharIter != name.entry.end()) {
+ diag->error(DiagMessage(symbol.source)
+ << "resource '" << name << "' has invalid entry name '"
+ << name.entry << "'. Invalid character '"
+ << StringPiece(badCharIter, 1) << "'");
+ return false;
+ }
- ResourceTablePackage* package = findOrCreatePackage(name.package);
- if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
- diag->error(DiagMessage(symbol.source)
- << "trying to add resource '"
- << name
- << "' with ID "
- << resId
- << " but package '"
- << package->name
- << "' already has ID "
- << std::hex << (int) package->id.value() << std::dec);
- return false;
- }
+ ResourceTablePackage* package = findOrCreatePackage(name.package);
+ if (resId.isValid() && package->id &&
+ package->id.value() != resId.packageId()) {
+ diag->error(DiagMessage(symbol.source)
+ << "trying to add resource '" << name << "' with ID " << resId
+ << " but package '" << package->name << "' already has ID "
+ << std::hex << (int)package->id.value() << std::dec);
+ return false;
+ }
- ResourceTableType* type = package->findOrCreateType(name.type);
- if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
- diag->error(DiagMessage(symbol.source)
- << "trying to add resource '"
- << name
- << "' with ID "
- << resId
- << " but type '"
- << type->type
- << "' already has ID "
- << std::hex << (int) type->id.value() << std::dec);
- return false;
- }
+ ResourceTableType* type = package->findOrCreateType(name.type);
+ if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
+ diag->error(DiagMessage(symbol.source)
+ << "trying to add resource '" << name << "' with ID " << resId
+ << " but type '" << type->type << "' already has ID "
+ << std::hex << (int)type->id.value() << std::dec);
+ return false;
+ }
- ResourceEntry* entry = type->findOrCreateEntry(name.entry);
- if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
- diag->error(DiagMessage(symbol.source)
- << "trying to add resource '"
- << name
- << "' with ID "
- << resId
- << " but resource already has ID "
- << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
- return false;
- }
+ ResourceEntry* entry = type->findOrCreateEntry(name.entry);
+ if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
+ diag->error(DiagMessage(symbol.source)
+ << "trying to add resource '" << name << "' with ID " << resId
+ << " but resource already has ID "
+ << ResourceId(package->id.value(), type->id.value(),
+ entry->id.value()));
+ return false;
+ }
- if (resId.isValid()) {
- package->id = resId.packageId();
- type->id = resId.typeId();
- entry->id = resId.entryId();
- }
+ if (resId.isValid()) {
+ package->id = resId.packageId();
+ type->id = resId.typeId();
+ entry->id = resId.entryId();
+ }
- // Only mark the type state as public, it doesn't care about being private.
- if (symbol.state == SymbolState::kPublic) {
- type->symbolStatus.state = SymbolState::kPublic;
- }
+ // Only mark the type state as public, it doesn't care about being private.
+ if (symbol.state == SymbolState::kPublic) {
+ type->symbolStatus.state = SymbolState::kPublic;
+ }
- if (symbol.state == SymbolState::kUndefined &&
- entry->symbolStatus.state != SymbolState::kUndefined) {
- // We can't undefine a symbol (remove its visibility). Ignore.
- return true;
- }
-
- if (symbol.state == SymbolState::kPrivate &&
- entry->symbolStatus.state == SymbolState::kPublic) {
- // We can't downgrade public to private. Ignore.
- return true;
- }
-
- entry->symbolStatus = std::move(symbol);
+ if (symbol.state == SymbolState::kUndefined &&
+ entry->symbolStatus.state != SymbolState::kUndefined) {
+ // We can't undefine a symbol (remove its visibility). Ignore.
return true;
+ }
+
+ if (symbol.state == SymbolState::kPrivate &&
+ entry->symbolStatus.state == SymbolState::kPublic) {
+ // We can't downgrade public to private. Ignore.
+ return true;
+ }
+
+ entry->symbolStatus = std::move(symbol);
+ return true;
}
-Maybe<ResourceTable::SearchResult>
-ResourceTable::findResource(const ResourceNameRef& name) {
- ResourceTablePackage* package = findPackage(name.package);
- if (!package) {
- return {};
- }
+Maybe<ResourceTable::SearchResult> ResourceTable::findResource(
+ const ResourceNameRef& name) {
+ ResourceTablePackage* package = findPackage(name.package);
+ if (!package) {
+ return {};
+ }
- ResourceTableType* type = package->findType(name.type);
- if (!type) {
- return {};
- }
+ ResourceTableType* type = package->findType(name.type);
+ if (!type) {
+ return {};
+ }
- ResourceEntry* entry = type->findEntry(name.entry);
- if (!entry) {
- return {};
- }
- return SearchResult{ package, type, entry };
+ ResourceEntry* entry = type->findEntry(name.entry);
+ if (!entry) {
+ return {};
+ }
+ return SearchResult{package, type, entry};
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 6c246d0..ebaad41 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -37,42 +37,43 @@
namespace aapt {
enum class SymbolState {
- kUndefined,
- kPrivate,
- kPublic,
+ kUndefined,
+ kPrivate,
+ kPublic,
};
/**
* The Public status of a resource.
*/
struct Symbol {
- SymbolState state = SymbolState::kUndefined;
- Source source;
- std::string comment;
+ SymbolState state = SymbolState::kUndefined;
+ Source source;
+ std::string comment;
};
class ResourceConfigValue {
-public:
- /**
- * The configuration for which this value is defined.
- */
- const ConfigDescription config;
+ public:
+ /**
+ * The configuration for which this value is defined.
+ */
+ const ConfigDescription config;
- /**
- * The product for which this value is defined.
- */
- const std::string product;
+ /**
+ * The product for which this value is defined.
+ */
+ const std::string product;
- /**
- * The actual Value.
- */
- std::unique_ptr<Value> value;
+ /**
+ * The actual Value.
+ */
+ std::unique_ptr<Value> value;
- ResourceConfigValue(const ConfigDescription& config, const StringPiece& product) :
- config(config), product(product.toString()) { }
+ ResourceConfigValue(const ConfigDescription& config,
+ const StringPiece& product)
+ : config(config), product(product.toString()) {}
-private:
- DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
};
/**
@@ -80,42 +81,44 @@
* varying values for each defined configuration.
*/
class ResourceEntry {
-public:
- /**
- * The name of the resource. Immutable, as
- * this determines the order of this resource
- * when doing lookups.
- */
- const std::string name;
+ public:
+ /**
+ * The name of the resource. Immutable, as
+ * this determines the order of this resource
+ * when doing lookups.
+ */
+ const std::string name;
- /**
- * The entry ID for this resource.
- */
- Maybe<uint16_t> id;
+ /**
+ * The entry ID for this resource.
+ */
+ Maybe<uint16_t> id;
- /**
- * Whether this resource is public (and must maintain the same entry ID across builds).
- */
- Symbol symbolStatus;
+ /**
+ * Whether this resource is public (and must maintain the same entry ID across
+ * builds).
+ */
+ Symbol symbolStatus;
- /**
- * The resource's values for each configuration.
- */
- std::vector<std::unique_ptr<ResourceConfigValue>> values;
+ /**
+ * The resource's values for each configuration.
+ */
+ std::vector<std::unique_ptr<ResourceConfigValue>> values;
- explicit ResourceEntry(const StringPiece& name) : name(name.toString()) { }
+ explicit ResourceEntry(const StringPiece& name) : name(name.toString()) {}
- ResourceConfigValue* findValue(const ConfigDescription& config);
- ResourceConfigValue* findValue(const ConfigDescription& config, const StringPiece& product);
- ResourceConfigValue* findOrCreateValue(const ConfigDescription& config,
- const StringPiece& product);
- std::vector<ResourceConfigValue*> findAllValues(const ConfigDescription& config);
- std::vector<ResourceConfigValue*> findValuesIf(
- const std::function<bool(ResourceConfigValue*)>& f);
+ ResourceConfigValue* findValue(const ConfigDescription& config);
+ ResourceConfigValue* findValue(const ConfigDescription& config,
+ const StringPiece& product);
+ ResourceConfigValue* findOrCreateValue(const ConfigDescription& config,
+ const StringPiece& product);
+ std::vector<ResourceConfigValue*> findAllValues(
+ const ConfigDescription& config);
+ std::vector<ResourceConfigValue*> findValuesIf(
+ const std::function<bool(ResourceConfigValue*)>& f);
-
-private:
- DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
};
/**
@@ -123,58 +126,53 @@
* for this type.
*/
class ResourceTableType {
-public:
- /**
- * The logical type of resource (string, drawable, layout, etc.).
- */
- const ResourceType type;
+ public:
+ /**
+ * The logical type of resource (string, drawable, layout, etc.).
+ */
+ const ResourceType type;
- /**
- * The type ID for this resource.
- */
- Maybe<uint8_t> id;
+ /**
+ * The type ID for this resource.
+ */
+ Maybe<uint8_t> id;
- /**
- * Whether this type is public (and must maintain the same
- * type ID across builds).
- */
- Symbol symbolStatus;
+ /**
+ * Whether this type is public (and must maintain the same
+ * type ID across builds).
+ */
+ Symbol symbolStatus;
- /**
- * List of resources for this type.
- */
- std::vector<std::unique_ptr<ResourceEntry>> entries;
+ /**
+ * List of resources for this type.
+ */
+ std::vector<std::unique_ptr<ResourceEntry>> entries;
- explicit ResourceTableType(const ResourceType type) : type(type) { }
+ explicit ResourceTableType(const ResourceType type) : type(type) {}
- ResourceEntry* findEntry(const StringPiece& name);
- ResourceEntry* findOrCreateEntry(const StringPiece& name);
+ ResourceEntry* findEntry(const StringPiece& name);
+ ResourceEntry* findOrCreateEntry(const StringPiece& name);
-private:
- DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
};
-enum class PackageType {
- System,
- Vendor,
- App,
- Dynamic
-};
+enum class PackageType { System, Vendor, App, Dynamic };
class ResourceTablePackage {
-public:
- PackageType type = PackageType::App;
- Maybe<uint8_t> id;
- std::string name;
+ public:
+ PackageType type = PackageType::App;
+ Maybe<uint8_t> id;
+ std::string name;
- std::vector<std::unique_ptr<ResourceTableType>> types;
+ std::vector<std::unique_ptr<ResourceTableType>> types;
- ResourceTablePackage() = default;
- ResourceTableType* findType(ResourceType type);
- ResourceTableType* findOrCreateType(const ResourceType type);
+ ResourceTablePackage() = default;
+ ResourceTableType* findType(ResourceType type);
+ ResourceTableType* findOrCreateType(const ResourceType type);
-private:
- DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
};
/**
@@ -182,140 +180,129 @@
* flattened into a binary resource table (resources.arsc).
*/
class ResourceTable {
-public:
- ResourceTable() = default;
+ public:
+ ResourceTable() = default;
- enum class CollisionResult {
- kKeepOriginal,
- kConflict,
- kTakeNew
- };
+ enum class CollisionResult { kKeepOriginal, kConflict, kTakeNew };
- using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>;
+ using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>;
- /**
- * When a collision of resources occurs, this method decides which value to keep.
- */
- static CollisionResult resolveValueCollision(Value* existing, Value* incoming);
+ /**
+ * When a collision of resources occurs, this method decides which value to
+ * keep.
+ */
+ static CollisionResult resolveValueCollision(Value* existing,
+ Value* incoming);
- bool addResource(const ResourceNameRef& name,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value,
- IDiagnostics* diag);
+ bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
+ const StringPiece& product, std::unique_ptr<Value> value,
+ IDiagnostics* diag);
- bool addResource(const ResourceNameRef& name,
- const ResourceId& resId,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value,
- IDiagnostics* diag);
+ bool addResource(const ResourceNameRef& name, const ResourceId& resId,
+ const ConfigDescription& config, const StringPiece& product,
+ std::unique_ptr<Value> value, IDiagnostics* diag);
- bool addFileReference(const ResourceNameRef& name,
- const ConfigDescription& config,
- const Source& source,
- const StringPiece& path,
- IDiagnostics* diag);
+ bool addFileReference(const ResourceNameRef& name,
+ const ConfigDescription& config, const Source& source,
+ const StringPiece& path, IDiagnostics* diag);
- bool addFileReferenceAllowMangled(const ResourceNameRef& name,
- const ConfigDescription& config,
- const Source& source,
- const StringPiece& path,
- io::IFile* file,
- IDiagnostics* diag);
-
- /**
- * Same as addResource, but doesn't verify the validity of the name. This is used
- * when loading resources from an existing binary resource table that may have mangled
- * names.
- */
- bool addResourceAllowMangled(const ResourceNameRef& name,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value,
- IDiagnostics* diag);
-
- bool addResourceAllowMangled(const ResourceNameRef& name,
- const ResourceId& id,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value,
- IDiagnostics* diag);
-
- bool setSymbolState(const ResourceNameRef& name,
- const ResourceId& resId,
- const Symbol& symbol,
- IDiagnostics* diag);
-
- bool setSymbolStateAllowMangled(const ResourceNameRef& name,
- const ResourceId& resId,
- const Symbol& symbol,
+ bool addFileReferenceAllowMangled(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Source& source,
+ const StringPiece& path, io::IFile* file,
IDiagnostics* diag);
- struct SearchResult {
- ResourceTablePackage* package;
- ResourceTableType* type;
- ResourceEntry* entry;
- };
+ /**
+ * Same as addResource, but doesn't verify the validity of the name. This is
+ * used
+ * when loading resources from an existing binary resource table that may have
+ * mangled
+ * names.
+ */
+ bool addResourceAllowMangled(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const StringPiece& product,
+ std::unique_ptr<Value> value,
+ IDiagnostics* diag);
- Maybe<SearchResult> findResource(const ResourceNameRef& name);
+ bool addResourceAllowMangled(const ResourceNameRef& name,
+ const ResourceId& id,
+ const ConfigDescription& config,
+ const StringPiece& product,
+ std::unique_ptr<Value> value,
+ IDiagnostics* diag);
- /**
- * The string pool used by this resource table. Values that reference strings must use
- * this pool to create their strings.
- *
- * NOTE: `stringPool` must come before `packages` so that it is destroyed after.
- * When `string pool` references are destroyed (as they will be when `packages`
- * is destroyed), they decrement a refCount, which would cause invalid
- * memory access if the pool was already destroyed.
- */
- StringPool stringPool;
+ bool setSymbolState(const ResourceNameRef& name, const ResourceId& resId,
+ const Symbol& symbol, IDiagnostics* diag);
- /**
- * The list of packages in this table, sorted alphabetically by package name.
- */
- std::vector<std::unique_ptr<ResourceTablePackage>> packages;
+ bool setSymbolStateAllowMangled(const ResourceNameRef& name,
+ const ResourceId& resId, const Symbol& symbol,
+ IDiagnostics* diag);
- /**
- * Returns the package struct with the given name, or nullptr if such a package does not
- * exist. The empty string is a valid package and typically is used to represent the
- * 'current' package before it is known to the ResourceTable.
- */
- ResourceTablePackage* findPackage(const StringPiece& name);
+ struct SearchResult {
+ ResourceTablePackage* package;
+ ResourceTableType* type;
+ ResourceEntry* entry;
+ };
- ResourceTablePackage* findPackageById(uint8_t id);
+ Maybe<SearchResult> findResource(const ResourceNameRef& name);
- ResourceTablePackage* createPackage(const StringPiece& name, Maybe<uint8_t> id = {});
+ /**
+ * The string pool used by this resource table. Values that reference strings
+ * must use
+ * this pool to create their strings.
+ *
+ * NOTE: `stringPool` must come before `packages` so that it is destroyed
+ * after.
+ * When `string pool` references are destroyed (as they will be when
+ * `packages`
+ * is destroyed), they decrement a refCount, which would cause invalid
+ * memory access if the pool was already destroyed.
+ */
+ StringPool stringPool;
-private:
- ResourceTablePackage* findOrCreatePackage(const StringPiece& name);
+ /**
+ * The list of packages in this table, sorted alphabetically by package name.
+ */
+ std::vector<std::unique_ptr<ResourceTablePackage>> packages;
- bool addResourceImpl(const ResourceNameRef& name,
- const ResourceId& resId,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value,
- const char* validChars,
- const CollisionResolverFunc& conflictResolver,
- IDiagnostics* diag);
+ /**
+ * Returns the package struct with the given name, or nullptr if such a
+ * package does not
+ * exist. The empty string is a valid package and typically is used to
+ * represent the
+ * 'current' package before it is known to the ResourceTable.
+ */
+ ResourceTablePackage* findPackage(const StringPiece& name);
- bool addFileReferenceImpl(const ResourceNameRef& name,
- const ConfigDescription& config,
- const Source& source,
- const StringPiece& path,
- io::IFile* file,
- const char* validChars,
- IDiagnostics* diag);
+ ResourceTablePackage* findPackageById(uint8_t id);
- bool setSymbolStateImpl(const ResourceNameRef& name,
- const ResourceId& resId,
- const Symbol& symbol,
- const char* validChars,
+ ResourceTablePackage* createPackage(const StringPiece& name,
+ Maybe<uint8_t> id = {});
+
+ private:
+ ResourceTablePackage* findOrCreatePackage(const StringPiece& name);
+
+ bool addResourceImpl(const ResourceNameRef& name, const ResourceId& resId,
+ const ConfigDescription& config,
+ const StringPiece& product, std::unique_ptr<Value> value,
+ const char* validChars,
+ const CollisionResolverFunc& conflictResolver,
+ IDiagnostics* diag);
+
+ bool addFileReferenceImpl(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Source& source, const StringPiece& path,
+ io::IFile* file, const char* validChars,
IDiagnostics* diag);
- DISALLOW_COPY_AND_ASSIGN(ResourceTable);
+ bool setSymbolStateImpl(const ResourceNameRef& name, const ResourceId& resId,
+ const Symbol& symbol, const char* validChars,
+ IDiagnostics* diag);
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceTable);
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_RESOURCE_TABLE_H
+#endif // AAPT_RESOURCE_TABLE_H
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 4db40a6..a64ad3e 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#include "Diagnostics.h"
#include "ResourceTable.h"
+#include "Diagnostics.h"
#include "ResourceValues.h"
#include "test/Test.h"
#include "util/Util.h"
@@ -27,124 +27,113 @@
namespace aapt {
TEST(ResourceTableTest, FailToAddResourceWithBadName) {
- ResourceTable table;
+ ResourceTable table;
- EXPECT_FALSE(table.addResource(
- test::parseNameOrDie("android:id/hey,there"),
- ConfigDescription{}, "",
- test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
- test::getDiagnostics()));
+ EXPECT_FALSE(table.addResource(
+ test::parseNameOrDie("android:id/hey,there"), ConfigDescription{}, "",
+ test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
+ test::getDiagnostics()));
- EXPECT_FALSE(table.addResource(
- test::parseNameOrDie("android:id/hey:there"),
- ConfigDescription{}, "",
- test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
- test::getDiagnostics()));
+ EXPECT_FALSE(table.addResource(
+ test::parseNameOrDie("android:id/hey:there"), ConfigDescription{}, "",
+ test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
+ test::getDiagnostics()));
}
TEST(ResourceTableTest, AddOneResource) {
- ResourceTable table;
+ ResourceTable table;
- EXPECT_TRUE(table.addResource(
- test::parseNameOrDie("android:attr/id"),
- ConfigDescription{}, "",
- test::ValueBuilder<Id>().setSource("test/path/file.xml", 23u).build(),
- test::getDiagnostics()));
+ EXPECT_TRUE(table.addResource(
+ test::parseNameOrDie("android:attr/id"), ConfigDescription{}, "",
+ test::ValueBuilder<Id>().setSource("test/path/file.xml", 23u).build(),
+ test::getDiagnostics()));
- ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/id"));
+ ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/id"));
}
TEST(ResourceTableTest, AddMultipleResources) {
- ResourceTable table;
+ ResourceTable table;
- ConfigDescription config;
- ConfigDescription languageConfig;
- memcpy(languageConfig.language, "pl", sizeof(languageConfig.language));
+ ConfigDescription config;
+ ConfigDescription languageConfig;
+ memcpy(languageConfig.language, "pl", sizeof(languageConfig.language));
- EXPECT_TRUE(table.addResource(
- test::parseNameOrDie("android:attr/layout_width"),
- config, "",
- test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(),
- test::getDiagnostics()));
+ EXPECT_TRUE(table.addResource(
+ test::parseNameOrDie("android:attr/layout_width"), config, "",
+ test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(),
+ test::getDiagnostics()));
- EXPECT_TRUE(table.addResource(
- test::parseNameOrDie("android:attr/id"),
- config, "",
- test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(),
- test::getDiagnostics()));
+ EXPECT_TRUE(table.addResource(
+ test::parseNameOrDie("android:attr/id"), config, "",
+ test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(),
+ test::getDiagnostics()));
- EXPECT_TRUE(table.addResource(
- test::parseNameOrDie("android:string/ok"),
- config, "",
- test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(),
- test::getDiagnostics()));
+ EXPECT_TRUE(table.addResource(
+ test::parseNameOrDie("android:string/ok"), config, "",
+ test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(),
+ test::getDiagnostics()));
- EXPECT_TRUE(table.addResource(
- test::parseNameOrDie("android:string/ok"),
- languageConfig, "",
- test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
- .setSource("test/path/file.xml", 20u)
- .build(),
- test::getDiagnostics()));
+ EXPECT_TRUE(table.addResource(
+ test::parseNameOrDie("android:string/ok"), languageConfig, "",
+ test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
+ .setSource("test/path/file.xml", 20u)
+ .build(),
+ test::getDiagnostics()));
- ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/layout_width"));
- ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/id"));
- ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:string/ok"));
- ASSERT_NE(nullptr, test::getValueForConfig<BinaryPrimitive>(&table, "android:string/ok",
- languageConfig));
+ ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/layout_width"));
+ ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/id"));
+ ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:string/ok"));
+ ASSERT_NE(nullptr, test::getValueForConfig<BinaryPrimitive>(
+ &table, "android:string/ok", languageConfig));
}
TEST(ResourceTableTest, OverrideWeakResourceValue) {
- ResourceTable table;
+ ResourceTable table;
- ASSERT_TRUE(table.addResource(
- test::parseNameOrDie("android:attr/foo"),
- ConfigDescription{}, "",
- util::make_unique<Attribute>(true),
- test::getDiagnostics()));
+ ASSERT_TRUE(table.addResource(
+ test::parseNameOrDie("android:attr/foo"), ConfigDescription{}, "",
+ util::make_unique<Attribute>(true), test::getDiagnostics()));
- Attribute* attr = test::getValue<Attribute>(&table, "android:attr/foo");
- ASSERT_NE(nullptr, attr);
- EXPECT_TRUE(attr->isWeak());
+ Attribute* attr = test::getValue<Attribute>(&table, "android:attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_TRUE(attr->isWeak());
- ASSERT_TRUE(table.addResource(
- test::parseNameOrDie("android:attr/foo"),
- ConfigDescription{}, "",
- util::make_unique<Attribute>(false),
- test::getDiagnostics()));
+ ASSERT_TRUE(table.addResource(
+ test::parseNameOrDie("android:attr/foo"), ConfigDescription{}, "",
+ util::make_unique<Attribute>(false), test::getDiagnostics()));
- attr = test::getValue<Attribute>(&table, "android:attr/foo");
- ASSERT_NE(nullptr, attr);
- EXPECT_FALSE(attr->isWeak());
+ attr = test::getValue<Attribute>(&table, "android:attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_FALSE(attr->isWeak());
}
TEST(ResourceTableTest, ProductVaryingValues) {
- ResourceTable table;
+ ResourceTable table;
- EXPECT_TRUE(table.addResource(test::parseNameOrDie("android:string/foo"),
- test::parseConfigOrDie("land"), "tablet",
- util::make_unique<Id>(),
- test::getDiagnostics()));
- EXPECT_TRUE(table.addResource(test::parseNameOrDie("android:string/foo"),
- test::parseConfigOrDie("land"), "phone",
- util::make_unique<Id>(),
- test::getDiagnostics()));
+ EXPECT_TRUE(table.addResource(test::parseNameOrDie("android:string/foo"),
+ test::parseConfigOrDie("land"), "tablet",
+ util::make_unique<Id>(),
+ test::getDiagnostics()));
+ EXPECT_TRUE(table.addResource(test::parseNameOrDie("android:string/foo"),
+ test::parseConfigOrDie("land"), "phone",
+ util::make_unique<Id>(),
+ test::getDiagnostics()));
- EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/foo",
- test::parseConfigOrDie("land"),
- "tablet"));
- EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/foo",
- test::parseConfigOrDie("land"),
- "phone"));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(
+ &table, "android:string/foo",
+ test::parseConfigOrDie("land"), "tablet"));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(
+ &table, "android:string/foo",
+ test::parseConfigOrDie("land"), "phone"));
- Maybe<ResourceTable::SearchResult> sr = table.findResource(
- test::parseNameOrDie("android:string/foo"));
- AAPT_ASSERT_TRUE(sr);
- std::vector<ResourceConfigValue*> values = sr.value().entry->findAllValues(
- test::parseConfigOrDie("land"));
- ASSERT_EQ(2u, values.size());
- EXPECT_EQ(std::string("phone"), values[0]->product);
- EXPECT_EQ(std::string("tablet"), values[1]->product);
+ Maybe<ResourceTable::SearchResult> sr =
+ table.findResource(test::parseNameOrDie("android:string/foo"));
+ AAPT_ASSERT_TRUE(sr);
+ std::vector<ResourceConfigValue*> values =
+ sr.value().entry->findAllValues(test::parseConfigOrDie("land"));
+ ASSERT_EQ(2u, values.size());
+ EXPECT_EQ(std::string("phone"), values[0]->product);
+ EXPECT_EQ(std::string("tablet"), values[1]->product);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 73a194e..b41be4b 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#include "NameMangler.h"
#include "ResourceUtils.h"
+#include "NameMangler.h"
#include "SdkConstants.h"
#include "flatten/ResourceTypeExtensions.h"
#include "util/Files.h"
@@ -27,188 +27,196 @@
namespace aapt {
namespace ResourceUtils {
-Maybe<ResourceName> toResourceName(const android::ResTable::resource_name& nameIn) {
- ResourceName nameOut;
- if (!nameIn.package) {
- return {};
- }
+Maybe<ResourceName> toResourceName(
+ const android::ResTable::resource_name& nameIn) {
+ ResourceName nameOut;
+ if (!nameIn.package) {
+ return {};
+ }
- nameOut.package = util::utf16ToUtf8(StringPiece16(nameIn.package, nameIn.packageLen));
+ nameOut.package =
+ util::utf16ToUtf8(StringPiece16(nameIn.package, nameIn.packageLen));
- const ResourceType* type;
- if (nameIn.type) {
- type = parseResourceType(util::utf16ToUtf8(StringPiece16(nameIn.type, nameIn.typeLen)));
- } else if (nameIn.type8) {
- type = parseResourceType(StringPiece(nameIn.type8, nameIn.typeLen));
- } else {
- return {};
- }
+ const ResourceType* type;
+ if (nameIn.type) {
+ type = parseResourceType(
+ util::utf16ToUtf8(StringPiece16(nameIn.type, nameIn.typeLen)));
+ } else if (nameIn.type8) {
+ type = parseResourceType(StringPiece(nameIn.type8, nameIn.typeLen));
+ } else {
+ return {};
+ }
- if (!type) {
- return {};
- }
+ if (!type) {
+ return {};
+ }
- nameOut.type = *type;
+ nameOut.type = *type;
- if (nameIn.name) {
- nameOut.entry = util::utf16ToUtf8(StringPiece16(nameIn.name, nameIn.nameLen));
- } else if (nameIn.name8) {
- nameOut.entry = StringPiece(nameIn.name8, nameIn.nameLen).toString();
- } else {
- return {};
- }
- return nameOut;
+ if (nameIn.name) {
+ nameOut.entry =
+ util::utf16ToUtf8(StringPiece16(nameIn.name, nameIn.nameLen));
+ } else if (nameIn.name8) {
+ nameOut.entry = StringPiece(nameIn.name8, nameIn.nameLen).toString();
+ } else {
+ return {};
+ }
+ return nameOut;
}
bool extractResourceName(const StringPiece& str, StringPiece* outPackage,
StringPiece* outType, StringPiece* outEntry) {
- bool hasPackageSeparator = false;
- bool hasTypeSeparator = false;
- const char* start = str.data();
- const char* end = start + str.size();
- const char* current = start;
- while (current != end) {
- if (outType->size() == 0 && *current == '/') {
- hasTypeSeparator = true;
- outType->assign(start, current - start);
- start = current + 1;
- } else if (outPackage->size() == 0 && *current == ':') {
- hasPackageSeparator = true;
- outPackage->assign(start, current - start);
- start = current + 1;
- }
- current++;
+ bool hasPackageSeparator = false;
+ bool hasTypeSeparator = false;
+ const char* start = str.data();
+ const char* end = start + str.size();
+ const char* current = start;
+ while (current != end) {
+ if (outType->size() == 0 && *current == '/') {
+ hasTypeSeparator = true;
+ outType->assign(start, current - start);
+ start = current + 1;
+ } else if (outPackage->size() == 0 && *current == ':') {
+ hasPackageSeparator = true;
+ outPackage->assign(start, current - start);
+ start = current + 1;
}
- outEntry->assign(start, end - start);
+ current++;
+ }
+ outEntry->assign(start, end - start);
- return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty());
+ return !(hasPackageSeparator && outPackage->empty()) &&
+ !(hasTypeSeparator && outType->empty());
}
-bool parseResourceName(const StringPiece& str, ResourceNameRef* outRef, bool* outPrivate) {
- if (str.empty()) {
- return false;
+bool parseResourceName(const StringPiece& str, ResourceNameRef* outRef,
+ bool* outPrivate) {
+ if (str.empty()) {
+ return false;
+ }
+
+ size_t offset = 0;
+ bool priv = false;
+ if (str.data()[0] == '*') {
+ priv = true;
+ offset = 1;
+ }
+
+ StringPiece package;
+ StringPiece type;
+ StringPiece entry;
+ if (!extractResourceName(str.substr(offset, str.size() - offset), &package,
+ &type, &entry)) {
+ return false;
+ }
+
+ const ResourceType* parsedType = parseResourceType(type);
+ if (!parsedType) {
+ return false;
+ }
+
+ if (entry.empty()) {
+ return false;
+ }
+
+ if (outRef) {
+ outRef->package = package;
+ outRef->type = *parsedType;
+ outRef->entry = entry;
+ }
+
+ if (outPrivate) {
+ *outPrivate = priv;
+ }
+ return true;
+}
+
+bool parseReference(const StringPiece& str, ResourceNameRef* outRef,
+ bool* outCreate, bool* outPrivate) {
+ StringPiece trimmedStr(util::trimWhitespace(str));
+ if (trimmedStr.empty()) {
+ return false;
+ }
+
+ bool create = false;
+ bool priv = false;
+ if (trimmedStr.data()[0] == '@') {
+ size_t offset = 1;
+ if (trimmedStr.data()[1] == '+') {
+ create = true;
+ offset += 1;
}
- size_t offset = 0;
- bool priv = false;
- if (str.data()[0] == '*') {
- priv = true;
- offset = 1;
+ ResourceNameRef name;
+ if (!parseResourceName(
+ trimmedStr.substr(offset, trimmedStr.size() - offset), &name,
+ &priv)) {
+ return false;
}
- StringPiece package;
- StringPiece type;
- StringPiece entry;
- if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) {
- return false;
+ if (create && priv) {
+ return false;
}
- const ResourceType* parsedType = parseResourceType(type);
- if (!parsedType) {
- return false;
- }
-
- if (entry.empty()) {
- return false;
+ if (create && name.type != ResourceType::kId) {
+ return false;
}
if (outRef) {
- outRef->package = package;
- outRef->type = *parsedType;
- outRef->entry = entry;
+ *outRef = name;
+ }
+
+ if (outCreate) {
+ *outCreate = create;
}
if (outPrivate) {
- *outPrivate = priv;
+ *outPrivate = priv;
}
return true;
-}
-
-bool parseReference(const StringPiece& str, ResourceNameRef* outRef, bool* outCreate,
- bool* outPrivate) {
- StringPiece trimmedStr(util::trimWhitespace(str));
- if (trimmedStr.empty()) {
- return false;
- }
-
- bool create = false;
- bool priv = false;
- if (trimmedStr.data()[0] == '@') {
- size_t offset = 1;
- if (trimmedStr.data()[1] == '+') {
- create = true;
- offset += 1;
- }
-
- ResourceNameRef name;
- if (!parseResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
- &name, &priv)) {
- return false;
- }
-
- if (create && priv) {
- return false;
- }
-
- if (create && name.type != ResourceType::kId) {
- return false;
- }
-
- if (outRef) {
- *outRef = name;
- }
-
- if (outCreate) {
- *outCreate = create;
- }
-
- if (outPrivate) {
- *outPrivate = priv;
- }
- return true;
- }
- return false;
+ }
+ return false;
}
bool isReference(const StringPiece& str) {
- return parseReference(str, nullptr, nullptr, nullptr);
+ return parseReference(str, nullptr, nullptr, nullptr);
}
bool parseAttributeReference(const StringPiece& str, ResourceNameRef* outRef) {
- StringPiece trimmedStr(util::trimWhitespace(str));
- if (trimmedStr.empty()) {
- return false;
- }
-
- if (*trimmedStr.data() == '?') {
- StringPiece package;
- StringPiece type;
- StringPiece entry;
- if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1),
- &package, &type, &entry)) {
- return false;
- }
-
- if (!type.empty() && type != "attr") {
- return false;
- }
-
- if (entry.empty()) {
- return false;
- }
-
- if (outRef) {
- outRef->package = package;
- outRef->type = ResourceType::kAttr;
- outRef->entry = entry;
- }
- return true;
- }
+ StringPiece trimmedStr(util::trimWhitespace(str));
+ if (trimmedStr.empty()) {
return false;
+ }
+
+ if (*trimmedStr.data() == '?') {
+ StringPiece package;
+ StringPiece type;
+ StringPiece entry;
+ if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1),
+ &package, &type, &entry)) {
+ return false;
+ }
+
+ if (!type.empty() && type != "attr") {
+ return false;
+ }
+
+ if (entry.empty()) {
+ return false;
+ }
+
+ if (outRef) {
+ outRef->package = package;
+ outRef->type = ResourceType::kAttr;
+ outRef->entry = entry;
+ }
+ return true;
+ }
+ return false;
}
bool isAttributeReference(const StringPiece& str) {
- return parseAttributeReference(str, nullptr);
+ return parseAttributeReference(str, nullptr);
}
/*
@@ -219,414 +227,421 @@
* <[*]package>:[style/]<entry>
* [[*]package:style/]<entry>
*/
-Maybe<Reference> parseStyleParentReference(const StringPiece& str, std::string* outError) {
- if (str.empty()) {
- return {};
+Maybe<Reference> parseStyleParentReference(const StringPiece& str,
+ std::string* outError) {
+ if (str.empty()) {
+ return {};
+ }
+
+ StringPiece name = str;
+
+ bool hasLeadingIdentifiers = false;
+ bool privateRef = false;
+
+ // Skip over these identifiers. A style's parent is a normal reference.
+ if (name.data()[0] == '@' || name.data()[0] == '?') {
+ hasLeadingIdentifiers = true;
+ name = name.substr(1, name.size() - 1);
+ }
+
+ if (name.data()[0] == '*') {
+ privateRef = true;
+ name = name.substr(1, name.size() - 1);
+ }
+
+ ResourceNameRef ref;
+ ref.type = ResourceType::kStyle;
+
+ StringPiece typeStr;
+ extractResourceName(name, &ref.package, &typeStr, &ref.entry);
+ if (!typeStr.empty()) {
+ // If we have a type, make sure it is a Style.
+ const ResourceType* parsedType = parseResourceType(typeStr);
+ if (!parsedType || *parsedType != ResourceType::kStyle) {
+ std::stringstream err;
+ err << "invalid resource type '" << typeStr << "' for parent of style";
+ *outError = err.str();
+ return {};
}
+ }
- StringPiece name = str;
+ if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
+ std::stringstream err;
+ err << "invalid parent reference '" << str << "'";
+ *outError = err.str();
+ return {};
+ }
- bool hasLeadingIdentifiers = false;
- bool privateRef = false;
-
- // Skip over these identifiers. A style's parent is a normal reference.
- if (name.data()[0] == '@' || name.data()[0] == '?') {
- hasLeadingIdentifiers = true;
- name = name.substr(1, name.size() - 1);
- }
-
- if (name.data()[0] == '*') {
- privateRef = true;
- name = name.substr(1, name.size() - 1);
- }
-
- ResourceNameRef ref;
- ref.type = ResourceType::kStyle;
-
- StringPiece typeStr;
- extractResourceName(name, &ref.package, &typeStr, &ref.entry);
- if (!typeStr.empty()) {
- // If we have a type, make sure it is a Style.
- const ResourceType* parsedType = parseResourceType(typeStr);
- if (!parsedType || *parsedType != ResourceType::kStyle) {
- std::stringstream err;
- err << "invalid resource type '" << typeStr << "' for parent of style";
- *outError = err.str();
- return {};
- }
- }
-
- if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
- std::stringstream err;
- err << "invalid parent reference '" << str << "'";
- *outError = err.str();
- return {};
- }
-
- Reference result(ref);
- result.privateReference = privateRef;
- return result;
+ Reference result(ref);
+ result.privateReference = privateRef;
+ return result;
}
Maybe<Reference> parseXmlAttributeName(const StringPiece& str) {
- StringPiece trimmedStr = util::trimWhitespace(str);
- const char* start = trimmedStr.data();
- const char* const end = start + trimmedStr.size();
- const char* p = start;
+ StringPiece trimmedStr = util::trimWhitespace(str);
+ const char* start = trimmedStr.data();
+ const char* const end = start + trimmedStr.size();
+ const char* p = start;
- Reference ref;
- if (p != end && *p == '*') {
- ref.privateReference = true;
- start++;
- p++;
+ Reference ref;
+ if (p != end && *p == '*') {
+ ref.privateReference = true;
+ start++;
+ p++;
+ }
+
+ StringPiece package;
+ StringPiece name;
+ while (p != end) {
+ if (*p == ':') {
+ package = StringPiece(start, p - start);
+ name = StringPiece(p + 1, end - (p + 1));
+ break;
}
+ p++;
+ }
- StringPiece package;
- StringPiece name;
- while (p != end) {
- if (*p == ':') {
- package = StringPiece(start, p - start);
- name = StringPiece(p + 1, end - (p + 1));
- break;
- }
- p++;
- }
-
- ref.name = ResourceName(package.toString(), ResourceType::kAttr,
- name.empty() ? trimmedStr.toString() : name.toString());
- return Maybe<Reference>(std::move(ref));
+ ref.name =
+ ResourceName(package.toString(), ResourceType::kAttr,
+ name.empty() ? trimmedStr.toString() : name.toString());
+ return Maybe<Reference>(std::move(ref));
}
-std::unique_ptr<Reference> tryParseReference(const StringPiece& str, bool* outCreate) {
- ResourceNameRef ref;
- bool privateRef = false;
- if (parseReference(str, &ref, outCreate, &privateRef)) {
- std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
- value->privateReference = privateRef;
- return value;
- }
+std::unique_ptr<Reference> tryParseReference(const StringPiece& str,
+ bool* outCreate) {
+ ResourceNameRef ref;
+ bool privateRef = false;
+ if (parseReference(str, &ref, outCreate, &privateRef)) {
+ std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
+ value->privateReference = privateRef;
+ return value;
+ }
- if (parseAttributeReference(str, &ref)) {
- if (outCreate) {
- *outCreate = false;
- }
- return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
+ if (parseAttributeReference(str, &ref)) {
+ if (outCreate) {
+ *outCreate = false;
}
- return {};
+ return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
+ }
+ return {};
}
std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece& str) {
- StringPiece trimmedStr(util::trimWhitespace(str));
- android::Res_value value = { };
- if (trimmedStr == "@null") {
- // TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
- // Instead we set the data type to TYPE_REFERENCE with a value of 0.
- value.dataType = android::Res_value::TYPE_REFERENCE;
- } else if (trimmedStr == "@empty") {
- // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
- value.dataType = android::Res_value::TYPE_NULL;
- value.data = android::Res_value::DATA_NULL_EMPTY;
- } else {
- return {};
- }
- return util::make_unique<BinaryPrimitive>(value);
+ StringPiece trimmedStr(util::trimWhitespace(str));
+ android::Res_value value = {};
+ if (trimmedStr == "@null") {
+ // TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
+ // Instead we set the data type to TYPE_REFERENCE with a value of 0.
+ value.dataType = android::Res_value::TYPE_REFERENCE;
+ } else if (trimmedStr == "@empty") {
+ // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
+ value.dataType = android::Res_value::TYPE_NULL;
+ value.data = android::Res_value::DATA_NULL_EMPTY;
+ } else {
+ return {};
+ }
+ return util::make_unique<BinaryPrimitive>(value);
}
std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
const StringPiece& str) {
- StringPiece trimmedStr(util::trimWhitespace(str));
- for (const Attribute::Symbol& symbol : enumAttr->symbols) {
- // Enum symbols are stored as @package:id/symbol resources,
- // so we need to match against the 'entry' part of the identifier.
- const ResourceName& enumSymbolResourceName = symbol.symbol.name.value();
- if (trimmedStr == enumSymbolResourceName.entry) {
- android::Res_value value = { };
- value.dataType = android::Res_value::TYPE_INT_DEC;
- value.data = symbol.value;
- return util::make_unique<BinaryPrimitive>(value);
- }
+ StringPiece trimmedStr(util::trimWhitespace(str));
+ for (const Attribute::Symbol& symbol : enumAttr->symbols) {
+ // Enum symbols are stored as @package:id/symbol resources,
+ // so we need to match against the 'entry' part of the identifier.
+ const ResourceName& enumSymbolResourceName = symbol.symbol.name.value();
+ if (trimmedStr == enumSymbolResourceName.entry) {
+ android::Res_value value = {};
+ value.dataType = android::Res_value::TYPE_INT_DEC;
+ value.data = symbol.value;
+ return util::make_unique<BinaryPrimitive>(value);
}
- return {};
+ }
+ return {};
}
std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr,
const StringPiece& str) {
- android::Res_value flags = { };
- flags.dataType = android::Res_value::TYPE_INT_HEX;
- flags.data = 0u;
+ android::Res_value flags = {};
+ flags.dataType = android::Res_value::TYPE_INT_HEX;
+ flags.data = 0u;
- if (util::trimWhitespace(str).empty()) {
- // Empty string is a valid flag (0).
- return util::make_unique<BinaryPrimitive>(flags);
- }
-
- for (StringPiece part : util::tokenize(str, '|')) {
- StringPiece trimmedPart = util::trimWhitespace(part);
-
- bool flagSet = false;
- for (const Attribute::Symbol& symbol : flagAttr->symbols) {
- // Flag symbols are stored as @package:id/symbol resources,
- // so we need to match against the 'entry' part of the identifier.
- const ResourceName& flagSymbolResourceName = symbol.symbol.name.value();
- if (trimmedPart == flagSymbolResourceName.entry) {
- flags.data |= symbol.value;
- flagSet = true;
- break;
- }
- }
-
- if (!flagSet) {
- return {};
- }
- }
+ if (util::trimWhitespace(str).empty()) {
+ // Empty string is a valid flag (0).
return util::make_unique<BinaryPrimitive>(flags);
+ }
+
+ for (StringPiece part : util::tokenize(str, '|')) {
+ StringPiece trimmedPart = util::trimWhitespace(part);
+
+ bool flagSet = false;
+ for (const Attribute::Symbol& symbol : flagAttr->symbols) {
+ // Flag symbols are stored as @package:id/symbol resources,
+ // so we need to match against the 'entry' part of the identifier.
+ const ResourceName& flagSymbolResourceName = symbol.symbol.name.value();
+ if (trimmedPart == flagSymbolResourceName.entry) {
+ flags.data |= symbol.value;
+ flagSet = true;
+ break;
+ }
+ }
+
+ if (!flagSet) {
+ return {};
+ }
+ }
+ return util::make_unique<BinaryPrimitive>(flags);
}
static uint32_t parseHex(char c, bool* outError) {
- if (c >= '0' && c <= '9') {
- return c - '0';
- } else if (c >= 'a' && c <= 'f') {
- return c - 'a' + 0xa;
- } else if (c >= 'A' && c <= 'F') {
- return c - 'A' + 0xa;
- } else {
- *outError = true;
- return 0xffffffffu;
- }
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ return c - 'a' + 0xa;
+ } else if (c >= 'A' && c <= 'F') {
+ return c - 'A' + 0xa;
+ } else {
+ *outError = true;
+ return 0xffffffffu;
+ }
}
std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece& str) {
- StringPiece colorStr(util::trimWhitespace(str));
- const char* start = colorStr.data();
- const size_t len = colorStr.size();
- if (len == 0 || start[0] != '#') {
- return {};
- }
+ StringPiece colorStr(util::trimWhitespace(str));
+ const char* start = colorStr.data();
+ const size_t len = colorStr.size();
+ if (len == 0 || start[0] != '#') {
+ return {};
+ }
- android::Res_value value = { };
- bool error = false;
- if (len == 4) {
- value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
- value.data = 0xff000000u;
- value.data |= parseHex(start[1], &error) << 20;
- value.data |= parseHex(start[1], &error) << 16;
- value.data |= parseHex(start[2], &error) << 12;
- value.data |= parseHex(start[2], &error) << 8;
- value.data |= parseHex(start[3], &error) << 4;
- value.data |= parseHex(start[3], &error);
- } else if (len == 5) {
- value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
- value.data |= parseHex(start[1], &error) << 28;
- value.data |= parseHex(start[1], &error) << 24;
- value.data |= parseHex(start[2], &error) << 20;
- value.data |= parseHex(start[2], &error) << 16;
- value.data |= parseHex(start[3], &error) << 12;
- value.data |= parseHex(start[3], &error) << 8;
- value.data |= parseHex(start[4], &error) << 4;
- value.data |= parseHex(start[4], &error);
- } else if (len == 7) {
- value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
- value.data = 0xff000000u;
- value.data |= parseHex(start[1], &error) << 20;
- value.data |= parseHex(start[2], &error) << 16;
- value.data |= parseHex(start[3], &error) << 12;
- value.data |= parseHex(start[4], &error) << 8;
- value.data |= parseHex(start[5], &error) << 4;
- value.data |= parseHex(start[6], &error);
- } else if (len == 9) {
- value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
- value.data |= parseHex(start[1], &error) << 28;
- value.data |= parseHex(start[2], &error) << 24;
- value.data |= parseHex(start[3], &error) << 20;
- value.data |= parseHex(start[4], &error) << 16;
- value.data |= parseHex(start[5], &error) << 12;
- value.data |= parseHex(start[6], &error) << 8;
- value.data |= parseHex(start[7], &error) << 4;
- value.data |= parseHex(start[8], &error);
- } else {
- return {};
- }
- return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
+ android::Res_value value = {};
+ bool error = false;
+ if (len == 4) {
+ value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
+ value.data = 0xff000000u;
+ value.data |= parseHex(start[1], &error) << 20;
+ value.data |= parseHex(start[1], &error) << 16;
+ value.data |= parseHex(start[2], &error) << 12;
+ value.data |= parseHex(start[2], &error) << 8;
+ value.data |= parseHex(start[3], &error) << 4;
+ value.data |= parseHex(start[3], &error);
+ } else if (len == 5) {
+ value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
+ value.data |= parseHex(start[1], &error) << 28;
+ value.data |= parseHex(start[1], &error) << 24;
+ value.data |= parseHex(start[2], &error) << 20;
+ value.data |= parseHex(start[2], &error) << 16;
+ value.data |= parseHex(start[3], &error) << 12;
+ value.data |= parseHex(start[3], &error) << 8;
+ value.data |= parseHex(start[4], &error) << 4;
+ value.data |= parseHex(start[4], &error);
+ } else if (len == 7) {
+ value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
+ value.data = 0xff000000u;
+ value.data |= parseHex(start[1], &error) << 20;
+ value.data |= parseHex(start[2], &error) << 16;
+ value.data |= parseHex(start[3], &error) << 12;
+ value.data |= parseHex(start[4], &error) << 8;
+ value.data |= parseHex(start[5], &error) << 4;
+ value.data |= parseHex(start[6], &error);
+ } else if (len == 9) {
+ value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
+ value.data |= parseHex(start[1], &error) << 28;
+ value.data |= parseHex(start[2], &error) << 24;
+ value.data |= parseHex(start[3], &error) << 20;
+ value.data |= parseHex(start[4], &error) << 16;
+ value.data |= parseHex(start[5], &error) << 12;
+ value.data |= parseHex(start[6], &error) << 8;
+ value.data |= parseHex(start[7], &error) << 4;
+ value.data |= parseHex(start[8], &error);
+ } else {
+ return {};
+ }
+ return error ? std::unique_ptr<BinaryPrimitive>()
+ : util::make_unique<BinaryPrimitive>(value);
}
Maybe<bool> parseBool(const StringPiece& str) {
- StringPiece trimmedStr(util::trimWhitespace(str));
- if (trimmedStr == "true" || trimmedStr == "TRUE" || trimmedStr == "True") {
- return Maybe<bool>(true);
- } else if (trimmedStr == "false" || trimmedStr == "FALSE" || trimmedStr == "False") {
- return Maybe<bool>(false);
- }
- return {};
+ StringPiece trimmedStr(util::trimWhitespace(str));
+ if (trimmedStr == "true" || trimmedStr == "TRUE" || trimmedStr == "True") {
+ return Maybe<bool>(true);
+ } else if (trimmedStr == "false" || trimmedStr == "FALSE" ||
+ trimmedStr == "False") {
+ return Maybe<bool>(false);
+ }
+ return {};
}
Maybe<uint32_t> parseInt(const StringPiece& str) {
- std::u16string str16 = util::utf8ToUtf16(str);
- android::Res_value value;
- if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
- return value.data;
- }
- return {};
+ std::u16string str16 = util::utf8ToUtf16(str);
+ android::Res_value value;
+ if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
+ return value.data;
+ }
+ return {};
}
Maybe<ResourceId> parseResourceId(const StringPiece& str) {
- StringPiece trimmedStr(util::trimWhitespace(str));
+ StringPiece trimmedStr(util::trimWhitespace(str));
- std::u16string str16 = util::utf8ToUtf16(trimmedStr);
- android::Res_value value;
- if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
- if (value.dataType == android::Res_value::TYPE_INT_HEX) {
- ResourceId id(value.data);
- if (id.isValid()) {
- return id;
- }
- }
+ std::u16string str16 = util::utf8ToUtf16(trimmedStr);
+ android::Res_value value;
+ if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
+ if (value.dataType == android::Res_value::TYPE_INT_HEX) {
+ ResourceId id(value.data);
+ if (id.isValid()) {
+ return id;
+ }
}
- return {};
+ }
+ return {};
}
Maybe<int> parseSdkVersion(const StringPiece& str) {
- StringPiece trimmedStr(util::trimWhitespace(str));
+ StringPiece trimmedStr(util::trimWhitespace(str));
- std::u16string str16 = util::utf8ToUtf16(trimmedStr);
- android::Res_value value;
- if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
- return static_cast<int>(value.data);
- }
+ std::u16string str16 = util::utf8ToUtf16(trimmedStr);
+ android::Res_value value;
+ if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
+ return static_cast<int>(value.data);
+ }
- // Try parsing the code name.
- std::pair<StringPiece, int> entry = getDevelopmentSdkCodeNameAndVersion();
- if (entry.first == trimmedStr) {
- return entry.second;
- }
- return {};
+ // Try parsing the code name.
+ std::pair<StringPiece, int> entry = getDevelopmentSdkCodeNameAndVersion();
+ if (entry.first == trimmedStr) {
+ return entry.second;
+ }
+ return {};
}
std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece& str) {
- if (Maybe<bool> maybeResult = parseBool(str)) {
- android::Res_value value = {};
- value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
+ if (Maybe<bool> maybeResult = parseBool(str)) {
+ android::Res_value value = {};
+ value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
- if (maybeResult.value()) {
- value.data = 0xffffffffu;
- } else {
- value.data = 0;
- }
- return util::make_unique<BinaryPrimitive>(value);
+ if (maybeResult.value()) {
+ value.data = 0xffffffffu;
+ } else {
+ value.data = 0;
}
- return {};
+ return util::make_unique<BinaryPrimitive>(value);
+ }
+ return {};
}
std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece& str) {
- std::u16string str16 = util::utf8ToUtf16(str);
- android::Res_value value;
- if (!android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
- return {};
- }
- return util::make_unique<BinaryPrimitive>(value);
+ std::u16string str16 = util::utf8ToUtf16(str);
+ android::Res_value value;
+ if (!android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
+ return {};
+ }
+ return util::make_unique<BinaryPrimitive>(value);
}
std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece& str) {
- std::u16string str16 = util::utf8ToUtf16(str);
- android::Res_value value;
- if (!android::ResTable::stringToFloat(str16.data(), str16.size(), &value)) {
- return {};
- }
- return util::make_unique<BinaryPrimitive>(value);
+ std::u16string str16 = util::utf8ToUtf16(str);
+ android::Res_value value;
+ if (!android::ResTable::stringToFloat(str16.data(), str16.size(), &value)) {
+ return {};
+ }
+ return util::make_unique<BinaryPrimitive>(value);
}
uint32_t androidTypeToAttributeTypeMask(uint16_t type) {
- switch (type) {
+ switch (type) {
case android::Res_value::TYPE_NULL:
case android::Res_value::TYPE_REFERENCE:
case android::Res_value::TYPE_ATTRIBUTE:
case android::Res_value::TYPE_DYNAMIC_REFERENCE:
- return android::ResTable_map::TYPE_REFERENCE;
+ return android::ResTable_map::TYPE_REFERENCE;
case android::Res_value::TYPE_STRING:
- return android::ResTable_map::TYPE_STRING;
+ return android::ResTable_map::TYPE_STRING;
case android::Res_value::TYPE_FLOAT:
- return android::ResTable_map::TYPE_FLOAT;
+ return android::ResTable_map::TYPE_FLOAT;
case android::Res_value::TYPE_DIMENSION:
- return android::ResTable_map::TYPE_DIMENSION;
+ return android::ResTable_map::TYPE_DIMENSION;
case android::Res_value::TYPE_FRACTION:
- return android::ResTable_map::TYPE_FRACTION;
+ return android::ResTable_map::TYPE_FRACTION;
case android::Res_value::TYPE_INT_DEC:
case android::Res_value::TYPE_INT_HEX:
- return android::ResTable_map::TYPE_INTEGER | android::ResTable_map::TYPE_ENUM
- | android::ResTable_map::TYPE_FLAGS;
+ return android::ResTable_map::TYPE_INTEGER |
+ android::ResTable_map::TYPE_ENUM |
+ android::ResTable_map::TYPE_FLAGS;
case android::Res_value::TYPE_INT_BOOLEAN:
- return android::ResTable_map::TYPE_BOOLEAN;
+ return android::ResTable_map::TYPE_BOOLEAN;
case android::Res_value::TYPE_INT_COLOR_ARGB8:
case android::Res_value::TYPE_INT_COLOR_RGB8:
case android::Res_value::TYPE_INT_COLOR_ARGB4:
case android::Res_value::TYPE_INT_COLOR_RGB4:
- return android::ResTable_map::TYPE_COLOR;
+ return android::ResTable_map::TYPE_COLOR;
default:
- return 0;
- };
+ return 0;
+ };
}
std::unique_ptr<Item> tryParseItemForAttribute(
- const StringPiece& value,
- uint32_t typeMask,
- const std::function<void(const ResourceName&)>& onCreateReference) {
- std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
- if (nullOrEmpty) {
- return std::move(nullOrEmpty);
- }
+ const StringPiece& value, uint32_t typeMask,
+ const std::function<void(const ResourceName&)>& onCreateReference) {
+ std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
+ if (nullOrEmpty) {
+ return std::move(nullOrEmpty);
+ }
- bool create = false;
- std::unique_ptr<Reference> reference = tryParseReference(value, &create);
- if (reference) {
- if (create && onCreateReference) {
- onCreateReference(reference->name.value());
- }
- return std::move(reference);
+ bool create = false;
+ std::unique_ptr<Reference> reference = tryParseReference(value, &create);
+ if (reference) {
+ if (create && onCreateReference) {
+ onCreateReference(reference->name.value());
}
+ return std::move(reference);
+ }
- if (typeMask & android::ResTable_map::TYPE_COLOR) {
- // Try parsing this as a color.
- std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
- if (color) {
- return std::move(color);
- }
+ if (typeMask & android::ResTable_map::TYPE_COLOR) {
+ // Try parsing this as a color.
+ std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
+ if (color) {
+ return std::move(color);
}
+ }
- if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
- // Try parsing this as a boolean.
- std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
- if (boolean) {
- return std::move(boolean);
- }
+ if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+ // Try parsing this as a boolean.
+ std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
+ if (boolean) {
+ return std::move(boolean);
}
+ }
- if (typeMask & android::ResTable_map::TYPE_INTEGER) {
- // Try parsing this as an integer.
- std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
- if (integer) {
- return std::move(integer);
- }
+ if (typeMask & android::ResTable_map::TYPE_INTEGER) {
+ // Try parsing this as an integer.
+ std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
+ if (integer) {
+ return std::move(integer);
}
+ }
- const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT
- | android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FRACTION;
- if (typeMask & floatMask) {
- // Try parsing this as a float.
- std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
- if (floatingPoint) {
- if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
- return std::move(floatingPoint);
- }
- }
+ const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT |
+ android::ResTable_map::TYPE_DIMENSION |
+ android::ResTable_map::TYPE_FRACTION;
+ if (typeMask & floatMask) {
+ // Try parsing this as a float.
+ std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
+ if (floatingPoint) {
+ if (typeMask &
+ androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
+ return std::move(floatingPoint);
+ }
}
- return {};
+ }
+ return {};
}
/**
@@ -634,48 +649,50 @@
* allows.
*/
std::unique_ptr<Item> tryParseItemForAttribute(
- const StringPiece& str, const Attribute* attr,
- const std::function<void(const ResourceName&)>& onCreateReference) {
- const uint32_t typeMask = attr->typeMask;
- std::unique_ptr<Item> value = tryParseItemForAttribute(str, typeMask, onCreateReference);
- if (value) {
- return value;
- }
+ const StringPiece& str, const Attribute* attr,
+ const std::function<void(const ResourceName&)>& onCreateReference) {
+ const uint32_t typeMask = attr->typeMask;
+ std::unique_ptr<Item> value =
+ tryParseItemForAttribute(str, typeMask, onCreateReference);
+ if (value) {
+ return value;
+ }
- if (typeMask & android::ResTable_map::TYPE_ENUM) {
- // Try parsing this as an enum.
- std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
- if (enumValue) {
- return std::move(enumValue);
- }
+ if (typeMask & android::ResTable_map::TYPE_ENUM) {
+ // Try parsing this as an enum.
+ std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
+ if (enumValue) {
+ return std::move(enumValue);
}
+ }
- if (typeMask & android::ResTable_map::TYPE_FLAGS) {
- // Try parsing this as a flag.
- std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
- if (flagValue) {
- return std::move(flagValue);
- }
+ if (typeMask & android::ResTable_map::TYPE_FLAGS) {
+ // Try parsing this as a flag.
+ std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
+ if (flagValue) {
+ return std::move(flagValue);
}
- return {};
+ }
+ return {};
}
-std::string buildResourceFileName(const ResourceFile& resFile, const NameMangler* mangler) {
- std::stringstream out;
- out << "res/" << resFile.name.type;
- if (resFile.config != ConfigDescription{}) {
- out << "-" << resFile.config;
- }
- out << "/";
+std::string buildResourceFileName(const ResourceFile& resFile,
+ const NameMangler* mangler) {
+ std::stringstream out;
+ out << "res/" << resFile.name.type;
+ if (resFile.config != ConfigDescription{}) {
+ out << "-" << resFile.config;
+ }
+ out << "/";
- if (mangler && mangler->shouldMangle(resFile.name.package)) {
- out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry);
- } else {
- out << resFile.name.entry;
- }
- out << file::getExtension(resFile.source.path);
- return out.str();
+ if (mangler && mangler->shouldMangle(resFile.name.package)) {
+ out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry);
+ } else {
+ out << resFile.name.entry;
+ }
+ out << file::getExtension(resFile.source.path);
+ return out.str();
}
-} // namespace ResourceUtils
-} // namespace aapt
+} // namespace ResourceUtils
+} // namespace aapt
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 555203b..cebe47c 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -41,15 +41,18 @@
StringPiece* outType, StringPiece* outEntry);
/**
- * Returns true if the string was parsed as a resource name ([*][package:]type/name), with
- * `outResource` set to the parsed resource name and `outPrivate` set to true if a '*' prefix
+ * Returns true if the string was parsed as a resource name
+ * ([*][package:]type/name), with
+ * `outResource` set to the parsed resource name and `outPrivate` set to true if
+ * a '*' prefix
* was present.
*/
bool parseResourceName(const StringPiece& str, ResourceNameRef* outResource,
bool* outPrivate = nullptr);
/*
- * Returns true if the string was parsed as a reference (@[+][package:]type/name), with
+ * Returns true if the string was parsed as a reference
+ * (@[+][package:]type/name), with
* `outReference` set to the parsed reference.
*
* If '+' was present in the reference, `outCreate` is set to true.
@@ -59,28 +62,34 @@
bool* outCreate = nullptr, bool* outPrivate = nullptr);
/*
- * Returns true if the string is in the form of a resource reference (@[+][package:]type/name).
+ * Returns true if the string is in the form of a resource reference
+ * (@[+][package:]type/name).
*/
bool isReference(const StringPiece& 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 parseAttributeReference(const StringPiece& str, ResourceNameRef* outReference);
+bool parseAttributeReference(const StringPiece& str,
+ ResourceNameRef* outReference);
/**
- * Returns true if the string is in the form of an attribute reference(?[package:][type/]name).
+ * Returns true if the string is in the form of an attribute
+ * reference(?[package:][type/]name).
*/
bool isAttributeReference(const StringPiece& str);
/**
* Convert an android::ResTable::resource_name to an aapt::ResourceName struct.
*/
-Maybe<ResourceName> toResourceName(const android::ResTable::resource_name& name);
+Maybe<ResourceName> toResourceName(
+ const android::ResTable::resource_name& name);
/**
- * Returns a boolean value if the string is equal to TRUE, true, True, FALSE, false, or False.
+ * Returns a boolean value if the string is equal to TRUE, true, True, FALSE,
+ * false, or False.
*/
Maybe<bool> parseBool(const StringPiece& str);
@@ -100,18 +109,22 @@
Maybe<int> parseSdkVersion(const StringPiece& str);
/*
- * Returns a Reference, or None Maybe instance if the string `str` was parsed as a
+ * Returns a Reference, or None Maybe instance if the string `str` was parsed as
+ * a
* valid reference to a style.
- * The format for a style parent is slightly more flexible than a normal reference:
+ * The format for a style parent is slightly more flexible than a normal
+ * reference:
*
* @[package:]style/<entry> or
* ?[package:]style/<entry> or
* <package>:[style/]<entry>
*/
-Maybe<Reference> parseStyleParentReference(const StringPiece& str, std::string* outError);
+Maybe<Reference> parseStyleParentReference(const StringPiece& str,
+ std::string* outError);
/*
- * Returns a Reference if the string `str` was parsed as a valid XML attribute name.
+ * Returns a Reference if the string `str` was parsed as a valid XML attribute
+ * name.
* The valid format for an XML attribute name is:
*
* package:entry
@@ -119,14 +132,18 @@
Maybe<Reference> parseXmlAttributeName(const StringPiece& str);
/*
- * Returns a Reference object if the string was parsed as a resource or attribute reference,
- * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if
+ * Returns a Reference object if the string was parsed as a resource or
+ * attribute reference,
+ * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true
+ * if
* the '+' was present in the string.
*/
-std::unique_ptr<Reference> tryParseReference(const StringPiece& str, bool* outCreate = nullptr);
+std::unique_ptr<Reference> tryParseReference(const StringPiece& str,
+ bool* outCreate = nullptr);
/*
- * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed
+ * Returns a BinaryPrimitve object representing @null or @empty if the string
+ * was parsed
* as one.
*/
std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece& str);
@@ -138,13 +155,15 @@
std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece& str);
/*
- * Returns a BinaryPrimitve object representing a boolean if the string was parsed
+ * Returns a BinaryPrimitve object representing a boolean if the string was
+ * parsed
* as one.
*/
std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece& str);
/*
- * Returns a BinaryPrimitve object representing an integer if the string was parsed
+ * Returns a BinaryPrimitve object representing an integer if the string was
+ * parsed
* as one.
*/
std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece& str);
@@ -156,45 +175,51 @@
std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece& str);
/*
- * Returns a BinaryPrimitve object representing an enum symbol if the string was parsed
+ * Returns a BinaryPrimitve object representing an enum symbol if the string was
+ * parsed
* as one.
*/
std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
const StringPiece& str);
/*
- * Returns a BinaryPrimitve object representing a flag symbol if the string was parsed
+ * Returns a BinaryPrimitve object representing a flag symbol if the string was
+ * parsed
* as one.
*/
std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* enumAttr,
const StringPiece& str);
/*
- * Try to convert a string to an Item for the given attribute. The attribute will
+ * Try to convert a string to an Item for the given attribute. The attribute
+ * will
* restrict what values the string can be converted to.
* The callback function onCreateReference is called when the parsed item is a
* reference to an ID that must be created (@+id/foo).
*/
std::unique_ptr<Item> tryParseItemForAttribute(
- const StringPiece& value, const Attribute* attr,
- const std::function<void(const ResourceName&)>& onCreateReference = {});
+ const StringPiece& value, const Attribute* attr,
+ const std::function<void(const ResourceName&)>& onCreateReference = {});
std::unique_ptr<Item> tryParseItemForAttribute(
- const StringPiece& value, uint32_t typeMask,
- const std::function<void(const ResourceName&)>& onCreateReference = {});
+ const StringPiece& value, uint32_t typeMask,
+ const std::function<void(const ResourceName&)>& onCreateReference = {});
uint32_t androidTypeToAttributeTypeMask(uint16_t type);
/**
- * Returns a string path suitable for use within an APK. The path will look like:
+ * Returns a string path suitable for use within an APK. The path will look
+ * like:
*
* res/type[-config]/<name>.<ext>
*
- * Then name may be mangled if a NameMangler is supplied (can be nullptr) and the package
+ * Then name may be mangled if a NameMangler is supplied (can be nullptr) and
+ * the package
* requires mangling.
*/
-std::string buildResourceFileName(const ResourceFile& resFile, const NameMangler* mangler);
+std::string buildResourceFileName(const ResourceFile& resFile,
+ const NameMangler* mangler);
-} // namespace ResourceUtils
-} // namespace aapt
+} // namespace ResourceUtils
+} // namespace aapt
#endif /* AAPT_RESOURCEUTILS_H */
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index 894cfcf..eb62b1b 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -14,179 +14,188 @@
* limitations under the License.
*/
-#include "Resource.h"
#include "ResourceUtils.h"
+#include "Resource.h"
#include "test/Test.h"
namespace aapt {
TEST(ResourceUtilsTest, ParseBool) {
- EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("true"));
- EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("TRUE"));
- EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("True"));
- EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("false"));
- EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("FALSE"));
- EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("False"));
+ EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("true"));
+ EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("TRUE"));
+ EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("True"));
+ EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("false"));
+ EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("FALSE"));
+ EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("False"));
}
TEST(ResourceUtilsTest, ParseResourceName) {
- ResourceNameRef actual;
- bool actualPriv = false;
- EXPECT_TRUE(ResourceUtils::parseResourceName("android:color/foo", &actual, &actualPriv));
- EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual);
- EXPECT_FALSE(actualPriv);
+ ResourceNameRef actual;
+ bool actualPriv = false;
+ EXPECT_TRUE(ResourceUtils::parseResourceName("android:color/foo", &actual,
+ &actualPriv));
+ EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual);
+ EXPECT_FALSE(actualPriv);
- EXPECT_TRUE(ResourceUtils::parseResourceName("color/foo", &actual, &actualPriv));
- EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, "foo"), actual);
- EXPECT_FALSE(actualPriv);
+ EXPECT_TRUE(
+ ResourceUtils::parseResourceName("color/foo", &actual, &actualPriv));
+ EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, "foo"), actual);
+ EXPECT_FALSE(actualPriv);
- EXPECT_TRUE(ResourceUtils::parseResourceName("*android:color/foo", &actual, &actualPriv));
- EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual);
- EXPECT_TRUE(actualPriv);
+ EXPECT_TRUE(ResourceUtils::parseResourceName("*android:color/foo", &actual,
+ &actualPriv));
+ EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual);
+ EXPECT_TRUE(actualPriv);
- EXPECT_FALSE(ResourceUtils::parseResourceName(StringPiece(), &actual, &actualPriv));
+ EXPECT_FALSE(
+ ResourceUtils::parseResourceName(StringPiece(), &actual, &actualPriv));
}
TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) {
- ResourceNameRef expected({}, ResourceType::kColor, "foo");
- ResourceNameRef actual;
- bool create = false;
- bool privateRef = false;
- EXPECT_TRUE(ResourceUtils::parseReference("@color/foo", &actual, &create, &privateRef));
- EXPECT_EQ(expected, actual);
- EXPECT_FALSE(create);
- EXPECT_FALSE(privateRef);
+ ResourceNameRef expected({}, ResourceType::kColor, "foo");
+ ResourceNameRef actual;
+ bool create = false;
+ bool privateRef = false;
+ EXPECT_TRUE(ResourceUtils::parseReference("@color/foo", &actual, &create,
+ &privateRef));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_FALSE(privateRef);
}
TEST(ResourceUtilsTest, ParseReferenceWithPackage) {
- ResourceNameRef expected("android", ResourceType::kColor, "foo");
- ResourceNameRef actual;
- bool create = false;
- bool privateRef = false;
- EXPECT_TRUE(ResourceUtils::parseReference("@android:color/foo", &actual, &create,
- &privateRef));
- EXPECT_EQ(expected, actual);
- EXPECT_FALSE(create);
- EXPECT_FALSE(privateRef);
+ ResourceNameRef expected("android", ResourceType::kColor, "foo");
+ ResourceNameRef actual;
+ bool create = false;
+ bool privateRef = false;
+ EXPECT_TRUE(ResourceUtils::parseReference("@android:color/foo", &actual,
+ &create, &privateRef));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_FALSE(privateRef);
}
TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) {
- ResourceNameRef expected("android", ResourceType::kColor, "foo");
- ResourceNameRef actual;
- bool create = false;
- bool privateRef = false;
- EXPECT_TRUE(ResourceUtils::parseReference("\t @android:color/foo\n \n\t", &actual,
- &create, &privateRef));
- EXPECT_EQ(expected, actual);
- EXPECT_FALSE(create);
- EXPECT_FALSE(privateRef);
+ ResourceNameRef expected("android", ResourceType::kColor, "foo");
+ ResourceNameRef actual;
+ bool create = false;
+ bool privateRef = false;
+ EXPECT_TRUE(ResourceUtils::parseReference("\t @android:color/foo\n \n\t",
+ &actual, &create, &privateRef));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_FALSE(privateRef);
}
TEST(ResourceUtilsTest, ParseAutoCreateIdReference) {
- ResourceNameRef expected("android", ResourceType::kId, "foo");
- ResourceNameRef actual;
- bool create = false;
- bool privateRef = false;
- EXPECT_TRUE(ResourceUtils::parseReference("@+android:id/foo", &actual, &create,
- &privateRef));
- EXPECT_EQ(expected, actual);
- EXPECT_TRUE(create);
- EXPECT_FALSE(privateRef);
+ ResourceNameRef expected("android", ResourceType::kId, "foo");
+ ResourceNameRef actual;
+ bool create = false;
+ bool privateRef = false;
+ EXPECT_TRUE(ResourceUtils::parseReference("@+android:id/foo", &actual,
+ &create, &privateRef));
+ EXPECT_EQ(expected, actual);
+ EXPECT_TRUE(create);
+ EXPECT_FALSE(privateRef);
}
TEST(ResourceUtilsTest, ParsePrivateReference) {
- ResourceNameRef expected("android", ResourceType::kId, "foo");
- ResourceNameRef actual;
- bool create = false;
- bool privateRef = false;
- EXPECT_TRUE(ResourceUtils::parseReference("@*android:id/foo", &actual, &create,
- &privateRef));
- EXPECT_EQ(expected, actual);
- EXPECT_FALSE(create);
- EXPECT_TRUE(privateRef);
+ ResourceNameRef expected("android", ResourceType::kId, "foo");
+ ResourceNameRef actual;
+ bool create = false;
+ bool privateRef = false;
+ EXPECT_TRUE(ResourceUtils::parseReference("@*android:id/foo", &actual,
+ &create, &privateRef));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_TRUE(privateRef);
}
TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) {
- bool create = false;
- bool privateRef = false;
- ResourceNameRef actual;
- EXPECT_FALSE(ResourceUtils::parseReference("@+android:color/foo", &actual, &create,
- &privateRef));
+ bool create = false;
+ bool privateRef = false;
+ ResourceNameRef actual;
+ EXPECT_FALSE(ResourceUtils::parseReference("@+android:color/foo", &actual,
+ &create, &privateRef));
}
TEST(ResourceUtilsTest, ParseAttributeReferences) {
- EXPECT_TRUE(ResourceUtils::isAttributeReference("?android"));
- EXPECT_TRUE(ResourceUtils::isAttributeReference("?android:foo"));
- EXPECT_TRUE(ResourceUtils::isAttributeReference("?attr/foo"));
- EXPECT_TRUE(ResourceUtils::isAttributeReference("?android:attr/foo"));
+ EXPECT_TRUE(ResourceUtils::isAttributeReference("?android"));
+ EXPECT_TRUE(ResourceUtils::isAttributeReference("?android:foo"));
+ EXPECT_TRUE(ResourceUtils::isAttributeReference("?attr/foo"));
+ EXPECT_TRUE(ResourceUtils::isAttributeReference("?android:attr/foo"));
}
TEST(ResourceUtilsTest, FailParseIncompleteReference) {
- EXPECT_FALSE(ResourceUtils::isAttributeReference("?style/foo"));
- EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:style/foo"));
- EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:"));
- EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:attr/"));
- EXPECT_FALSE(ResourceUtils::isAttributeReference("?:attr/"));
- EXPECT_FALSE(ResourceUtils::isAttributeReference("?:attr/foo"));
- EXPECT_FALSE(ResourceUtils::isAttributeReference("?:/"));
- EXPECT_FALSE(ResourceUtils::isAttributeReference("?:/foo"));
- EXPECT_FALSE(ResourceUtils::isAttributeReference("?attr/"));
- EXPECT_FALSE(ResourceUtils::isAttributeReference("?/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference("?style/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:style/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:attr/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference("?:attr/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference("?:attr/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference("?:/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference("?:/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference("?attr/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference("?/foo"));
}
TEST(ResourceUtilsTest, ParseStyleParentReference) {
- const ResourceName kAndroidStyleFooName("android", ResourceType::kStyle, "foo");
- const ResourceName kStyleFooName({}, ResourceType::kStyle, "foo");
+ const ResourceName kAndroidStyleFooName("android", ResourceType::kStyle,
+ "foo");
+ const ResourceName kStyleFooName({}, ResourceType::kStyle, "foo");
- std::string errStr;
- Maybe<Reference> ref = ResourceUtils::parseStyleParentReference("@android:style/foo", &errStr);
- AAPT_ASSERT_TRUE(ref);
- EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+ std::string errStr;
+ Maybe<Reference> ref =
+ ResourceUtils::parseStyleParentReference("@android:style/foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
- ref = ResourceUtils::parseStyleParentReference("@style/foo", &errStr);
- AAPT_ASSERT_TRUE(ref);
- EXPECT_EQ(ref.value().name.value(), kStyleFooName);
+ ref = ResourceUtils::parseStyleParentReference("@style/foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kStyleFooName);
- ref = ResourceUtils::parseStyleParentReference("?android:style/foo", &errStr);
- AAPT_ASSERT_TRUE(ref);
- EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+ ref = ResourceUtils::parseStyleParentReference("?android:style/foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
- ref = ResourceUtils::parseStyleParentReference("?style/foo", &errStr);
- AAPT_ASSERT_TRUE(ref);
- EXPECT_EQ(ref.value().name.value(), kStyleFooName);
+ ref = ResourceUtils::parseStyleParentReference("?style/foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kStyleFooName);
- ref = ResourceUtils::parseStyleParentReference("android:style/foo", &errStr);
- AAPT_ASSERT_TRUE(ref);
- EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+ ref = ResourceUtils::parseStyleParentReference("android:style/foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
- ref = ResourceUtils::parseStyleParentReference("android:foo", &errStr);
- AAPT_ASSERT_TRUE(ref);
- EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+ ref = ResourceUtils::parseStyleParentReference("android:foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
- ref = ResourceUtils::parseStyleParentReference("@android:foo", &errStr);
- AAPT_ASSERT_TRUE(ref);
- EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+ ref = ResourceUtils::parseStyleParentReference("@android:foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
- ref = ResourceUtils::parseStyleParentReference("foo", &errStr);
- AAPT_ASSERT_TRUE(ref);
- EXPECT_EQ(ref.value().name.value(), kStyleFooName);
+ ref = ResourceUtils::parseStyleParentReference("foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kStyleFooName);
- ref = ResourceUtils::parseStyleParentReference("*android:style/foo", &errStr);
- AAPT_ASSERT_TRUE(ref);
- EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
- EXPECT_TRUE(ref.value().privateReference);
+ ref = ResourceUtils::parseStyleParentReference("*android:style/foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+ EXPECT_TRUE(ref.value().privateReference);
}
TEST(ResourceUtilsTest, ParseEmptyFlag) {
- std::unique_ptr<Attribute> attr = test::AttributeBuilder(false)
- .setTypeMask(android::ResTable_map::TYPE_FLAGS)
- .addItem("one", 0x01)
- .addItem("two", 0x02)
- .build();
+ std::unique_ptr<Attribute> attr =
+ test::AttributeBuilder(false)
+ .setTypeMask(android::ResTable_map::TYPE_FLAGS)
+ .addItem("one", 0x01)
+ .addItem("two", 0x02)
+ .build();
- std::unique_ptr<BinaryPrimitive> result = ResourceUtils::tryParseFlagSymbol(attr.get(), "");
- ASSERT_NE(nullptr, result);
- EXPECT_EQ(0u, result->value.data);
+ std::unique_ptr<BinaryPrimitive> result =
+ ResourceUtils::tryParseFlagSymbol(attr.get(), "");
+ ASSERT_NE(nullptr, result);
+ EXPECT_EQ(0u, result->value.data);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 492155d..60590b6 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -14,14 +14,14 @@
* limitations under the License.
*/
+#include "ResourceValues.h"
#include "Resource.h"
#include "ResourceUtils.h"
-#include "ResourceValues.h"
#include "ValueVisitor.h"
#include "util/Util.h"
-#include <algorithm>
#include <androidfw/ResourceTypes.h>
+#include <algorithm>
#include <limits>
#include <set>
@@ -29,742 +29,745 @@
template <typename Derived>
void BaseValue<Derived>::accept(RawValueVisitor* visitor) {
- visitor->visit(static_cast<Derived*>(this));
+ visitor->visit(static_cast<Derived*>(this));
}
template <typename Derived>
void BaseItem<Derived>::accept(RawValueVisitor* visitor) {
- visitor->visit(static_cast<Derived*>(this));
+ visitor->visit(static_cast<Derived*>(this));
}
-RawString::RawString(const StringPool::Ref& ref) : value(ref) {
-}
+RawString::RawString(const StringPool::Ref& ref) : value(ref) {}
bool RawString::equals(const Value* value) const {
- const RawString* other = valueCast<RawString>(value);
- if (!other) {
- return false;
- }
- return *this->value == *other->value;
+ const RawString* other = valueCast<RawString>(value);
+ if (!other) {
+ return false;
+ }
+ return *this->value == *other->value;
}
RawString* RawString::clone(StringPool* newPool) const {
- RawString* rs = new RawString(newPool->makeRef(*value));
- rs->mComment = mComment;
- rs->mSource = mSource;
- return rs;
+ RawString* rs = new RawString(newPool->makeRef(*value));
+ rs->mComment = mComment;
+ rs->mSource = mSource;
+ return rs;
}
bool RawString::flatten(android::Res_value* outValue) const {
- outValue->dataType = android::Res_value::TYPE_STRING;
- outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
- return true;
+ outValue->dataType = android::Res_value::TYPE_STRING;
+ outValue->data =
+ util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
+ return true;
}
void RawString::print(std::ostream* out) const {
- *out << "(raw string) " << *value;
+ *out << "(raw string) " << *value;
}
-Reference::Reference() : referenceType(Type::kResource) {
-}
+Reference::Reference() : referenceType(Type::kResource) {}
-Reference::Reference(const ResourceNameRef& n, Type t) :
- name(n.toResourceName()), referenceType(t) {
-}
+Reference::Reference(const ResourceNameRef& n, Type t)
+ : name(n.toResourceName()), referenceType(t) {}
-Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) {
-}
+Reference::Reference(const ResourceId& i, Type type)
+ : id(i), referenceType(type) {}
-Reference::Reference(const ResourceNameRef& n, const ResourceId& i) :
- name(n.toResourceName()), id(i), referenceType(Type::kResource) {
-}
+Reference::Reference(const ResourceNameRef& n, const ResourceId& i)
+ : name(n.toResourceName()), id(i), referenceType(Type::kResource) {}
bool Reference::equals(const Value* value) const {
- const Reference* other = valueCast<Reference>(value);
- if (!other) {
- return false;
- }
- return referenceType == other->referenceType && privateReference == other->privateReference &&
- id == other->id && name == other->name;
+ const Reference* other = valueCast<Reference>(value);
+ if (!other) {
+ return false;
+ }
+ return referenceType == other->referenceType &&
+ privateReference == other->privateReference && id == other->id &&
+ name == other->name;
}
bool Reference::flatten(android::Res_value* outValue) const {
- outValue->dataType = (referenceType == Reference::Type::kResource) ?
- android::Res_value::TYPE_REFERENCE : android::Res_value::TYPE_ATTRIBUTE;
- outValue->data = util::hostToDevice32(id ? id.value().id : 0);
- return true;
+ outValue->dataType = (referenceType == Reference::Type::kResource)
+ ? android::Res_value::TYPE_REFERENCE
+ : android::Res_value::TYPE_ATTRIBUTE;
+ outValue->data = util::hostToDevice32(id ? id.value().id : 0);
+ return true;
}
Reference* Reference::clone(StringPool* /*newPool*/) const {
- return new Reference(*this);
+ return new Reference(*this);
}
void Reference::print(std::ostream* out) const {
- *out << "(reference) ";
- if (referenceType == Reference::Type::kResource) {
- *out << "@";
- if (privateReference) {
- *out << "*";
- }
- } else {
- *out << "?";
+ *out << "(reference) ";
+ if (referenceType == Reference::Type::kResource) {
+ *out << "@";
+ if (privateReference) {
+ *out << "*";
}
+ } else {
+ *out << "?";
+ }
- if (name) {
- *out << name.value();
- }
+ if (name) {
+ *out << name.value();
+ }
- if (id && !Res_INTERNALID(id.value().id)) {
- *out << " " << id.value();
- }
+ if (id && !Res_INTERNALID(id.value().id)) {
+ *out << " " << id.value();
+ }
}
bool Id::equals(const Value* value) const {
- return valueCast<Id>(value) != nullptr;
+ return valueCast<Id>(value) != nullptr;
}
bool Id::flatten(android::Res_value* out) const {
- out->dataType = android::Res_value::TYPE_INT_BOOLEAN;
- out->data = util::hostToDevice32(0);
- return true;
+ out->dataType = android::Res_value::TYPE_INT_BOOLEAN;
+ out->data = util::hostToDevice32(0);
+ return true;
}
-Id* Id::clone(StringPool* /*newPool*/) const {
- return new Id(*this);
-}
+Id* Id::clone(StringPool* /*newPool*/) const { return new Id(*this); }
-void Id::print(std::ostream* out) const {
- *out << "(id)";
-}
+void Id::print(std::ostream* out) const { *out << "(id)"; }
-String::String(const StringPool::Ref& ref) : value(ref) {
-}
+String::String(const StringPool::Ref& ref) : value(ref) {}
bool String::equals(const Value* value) const {
- const String* other = valueCast<String>(value);
- if (!other) {
- return false;
- }
- return *this->value == *other->value;
+ const String* other = valueCast<String>(value);
+ if (!other) {
+ return false;
+ }
+ return *this->value == *other->value;
}
bool String::flatten(android::Res_value* outValue) const {
- // Verify that our StringPool index is within encode-able limits.
- if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
- return false;
- }
+ // Verify that our StringPool index is within encode-able limits.
+ if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
+ return false;
+ }
- outValue->dataType = android::Res_value::TYPE_STRING;
- outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
- return true;
+ outValue->dataType = android::Res_value::TYPE_STRING;
+ outValue->data =
+ util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
+ return true;
}
String* String::clone(StringPool* newPool) const {
- String* str = new String(newPool->makeRef(*value));
- str->mComment = mComment;
- str->mSource = mSource;
- return str;
+ String* str = new String(newPool->makeRef(*value));
+ str->mComment = mComment;
+ str->mSource = mSource;
+ return str;
}
void String::print(std::ostream* out) const {
- *out << "(string) \"" << *value << "\"";
+ *out << "(string) \"" << *value << "\"";
}
-StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {
-}
+StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {}
bool StyledString::equals(const Value* value) const {
- const StyledString* other = valueCast<StyledString>(value);
- if (!other) {
- return false;
- }
-
- if (*this->value->str == *other->value->str) {
- const std::vector<StringPool::Span>& spansA = this->value->spans;
- const std::vector<StringPool::Span>& spansB = other->value->spans;
- return std::equal(spansA.begin(), spansA.end(), spansB.begin(),
- [](const StringPool::Span& a, const StringPool::Span& b) -> bool {
- return *a.name == *b.name && a.firstChar == b.firstChar && a.lastChar == b.lastChar;
- });
- }
+ const StyledString* other = valueCast<StyledString>(value);
+ if (!other) {
return false;
+ }
+
+ if (*this->value->str == *other->value->str) {
+ const std::vector<StringPool::Span>& spansA = this->value->spans;
+ const std::vector<StringPool::Span>& spansB = other->value->spans;
+ return std::equal(
+ spansA.begin(), spansA.end(), spansB.begin(),
+ [](const StringPool::Span& a, const StringPool::Span& b) -> bool {
+ return *a.name == *b.name && a.firstChar == b.firstChar &&
+ a.lastChar == b.lastChar;
+ });
+ }
+ return false;
}
bool StyledString::flatten(android::Res_value* outValue) const {
- if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
- return false;
- }
+ if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
+ return false;
+ }
- outValue->dataType = android::Res_value::TYPE_STRING;
- outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
- return true;
+ outValue->dataType = android::Res_value::TYPE_STRING;
+ outValue->data =
+ util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
+ return true;
}
StyledString* StyledString::clone(StringPool* newPool) const {
- StyledString* str = new StyledString(newPool->makeRef(value));
- str->mComment = mComment;
- str->mSource = mSource;
- return str;
+ StyledString* str = new StyledString(newPool->makeRef(value));
+ str->mComment = mComment;
+ str->mSource = mSource;
+ return str;
}
void StyledString::print(std::ostream* out) const {
- *out << "(styled string) \"" << *value->str << "\"";
- for (const StringPool::Span& span : value->spans) {
- *out << " "<< *span.name << ":" << span.firstChar << "," << span.lastChar;
- }
+ *out << "(styled string) \"" << *value->str << "\"";
+ for (const StringPool::Span& span : value->spans) {
+ *out << " " << *span.name << ":" << span.firstChar << "," << span.lastChar;
+ }
}
-FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {
-}
+FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {}
bool FileReference::equals(const Value* value) const {
- const FileReference* other = valueCast<FileReference>(value);
- if (!other) {
- return false;
- }
- return *path == *other->path;
+ const FileReference* other = valueCast<FileReference>(value);
+ if (!other) {
+ return false;
+ }
+ return *path == *other->path;
}
bool FileReference::flatten(android::Res_value* outValue) const {
- if (path.getIndex() > std::numeric_limits<uint32_t>::max()) {
- return false;
- }
+ if (path.getIndex() > std::numeric_limits<uint32_t>::max()) {
+ return false;
+ }
- outValue->dataType = android::Res_value::TYPE_STRING;
- outValue->data = util::hostToDevice32(static_cast<uint32_t>(path.getIndex()));
- return true;
+ outValue->dataType = android::Res_value::TYPE_STRING;
+ outValue->data = util::hostToDevice32(static_cast<uint32_t>(path.getIndex()));
+ return true;
}
FileReference* FileReference::clone(StringPool* newPool) const {
- FileReference* fr = new FileReference(newPool->makeRef(*path));
- fr->file = file;
- fr->mComment = mComment;
- fr->mSource = mSource;
- return fr;
+ FileReference* fr = new FileReference(newPool->makeRef(*path));
+ fr->file = file;
+ fr->mComment = mComment;
+ fr->mSource = mSource;
+ return fr;
}
void FileReference::print(std::ostream* out) const {
- *out << "(file) " << *path;
+ *out << "(file) " << *path;
}
-BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {
-}
+BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {}
BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) {
- value.dataType = dataType;
- value.data = data;
+ value.dataType = dataType;
+ value.data = data;
}
bool BinaryPrimitive::equals(const Value* value) const {
- const BinaryPrimitive* other = valueCast<BinaryPrimitive>(value);
- if (!other) {
- return false;
- }
- return this->value.dataType == other->value.dataType && this->value.data == other->value.data;
+ const BinaryPrimitive* other = valueCast<BinaryPrimitive>(value);
+ if (!other) {
+ return false;
+ }
+ return this->value.dataType == other->value.dataType &&
+ this->value.data == other->value.data;
}
bool BinaryPrimitive::flatten(android::Res_value* outValue) const {
- outValue->dataType = value.dataType;
- outValue->data = util::hostToDevice32(value.data);
- return true;
+ outValue->dataType = value.dataType;
+ outValue->data = util::hostToDevice32(value.data);
+ return true;
}
BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const {
- return new BinaryPrimitive(*this);
+ return new BinaryPrimitive(*this);
}
void BinaryPrimitive::print(std::ostream* out) const {
- switch (value.dataType) {
- case android::Res_value::TYPE_NULL:
- *out << "(null)";
- break;
- case android::Res_value::TYPE_INT_DEC:
- *out << "(integer) " << static_cast<int32_t>(value.data);
- break;
- case android::Res_value::TYPE_INT_HEX:
- *out << "(integer) 0x" << std::hex << value.data << std::dec;
- break;
- case android::Res_value::TYPE_INT_BOOLEAN:
- *out << "(boolean) " << (value.data != 0 ? "true" : "false");
- break;
- case android::Res_value::TYPE_INT_COLOR_ARGB8:
- case android::Res_value::TYPE_INT_COLOR_RGB8:
- case android::Res_value::TYPE_INT_COLOR_ARGB4:
- case android::Res_value::TYPE_INT_COLOR_RGB4:
- *out << "(color) #" << std::hex << value.data << std::dec;
- break;
- default:
- *out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x"
- << std::hex << value.data << std::dec;
- break;
- }
+ switch (value.dataType) {
+ case android::Res_value::TYPE_NULL:
+ *out << "(null)";
+ break;
+ case android::Res_value::TYPE_INT_DEC:
+ *out << "(integer) " << static_cast<int32_t>(value.data);
+ break;
+ case android::Res_value::TYPE_INT_HEX:
+ *out << "(integer) 0x" << std::hex << value.data << std::dec;
+ break;
+ case android::Res_value::TYPE_INT_BOOLEAN:
+ *out << "(boolean) " << (value.data != 0 ? "true" : "false");
+ break;
+ case android::Res_value::TYPE_INT_COLOR_ARGB8:
+ case android::Res_value::TYPE_INT_COLOR_RGB8:
+ case android::Res_value::TYPE_INT_COLOR_ARGB4:
+ case android::Res_value::TYPE_INT_COLOR_RGB4:
+ *out << "(color) #" << std::hex << value.data << std::dec;
+ break;
+ default:
+ *out << "(unknown 0x" << std::hex << (int)value.dataType << ") 0x"
+ << std::hex << value.data << std::dec;
+ break;
+ }
}
-Attribute::Attribute(bool w, uint32_t t) :
- typeMask(t),
- minInt(std::numeric_limits<int32_t>::min()),
- maxInt(std::numeric_limits<int32_t>::max()) {
- mWeak = w;
+Attribute::Attribute(bool w, uint32_t t)
+ : typeMask(t),
+ minInt(std::numeric_limits<int32_t>::min()),
+ maxInt(std::numeric_limits<int32_t>::max()) {
+ mWeak = w;
}
template <typename T>
T* addPointer(T& val) {
- return &val;
+ return &val;
}
bool Attribute::equals(const Value* value) const {
- const Attribute* other = valueCast<Attribute>(value);
- if (!other) {
- return false;
- }
+ const Attribute* other = valueCast<Attribute>(value);
+ if (!other) {
+ return false;
+ }
- if (symbols.size() != other->symbols.size()) {
- return false;
- }
+ if (symbols.size() != other->symbols.size()) {
+ return false;
+ }
- if (typeMask != other->typeMask || minInt != other->minInt || maxInt != other->maxInt) {
- return false;
- }
+ if (typeMask != other->typeMask || minInt != other->minInt ||
+ maxInt != other->maxInt) {
+ return false;
+ }
- std::vector<const Symbol*> sortedA;
- std::transform(symbols.begin(), symbols.end(),
- std::back_inserter(sortedA), addPointer<const Symbol>);
- std::sort(sortedA.begin(), sortedA.end(), [](const Symbol* a, const Symbol* b) -> bool {
- return a->symbol.name < b->symbol.name;
- });
+ std::vector<const Symbol*> sortedA;
+ std::transform(symbols.begin(), symbols.end(), std::back_inserter(sortedA),
+ addPointer<const Symbol>);
+ std::sort(sortedA.begin(), sortedA.end(),
+ [](const Symbol* a, const Symbol* b) -> bool {
+ return a->symbol.name < b->symbol.name;
+ });
- std::vector<const Symbol*> sortedB;
- std::transform(other->symbols.begin(), other->symbols.end(),
- std::back_inserter(sortedB), addPointer<const Symbol>);
- std::sort(sortedB.begin(), sortedB.end(), [](const Symbol* a, const Symbol* b) -> bool {
- return a->symbol.name < b->symbol.name;
- });
+ std::vector<const Symbol*> sortedB;
+ std::transform(other->symbols.begin(), other->symbols.end(),
+ std::back_inserter(sortedB), addPointer<const Symbol>);
+ std::sort(sortedB.begin(), sortedB.end(),
+ [](const Symbol* a, const Symbol* b) -> bool {
+ return a->symbol.name < b->symbol.name;
+ });
- return std::equal(sortedA.begin(), sortedA.end(), sortedB.begin(),
- [](const Symbol* a, const Symbol* b) -> bool {
- return a->symbol.equals(&b->symbol) && a->value == b->value;
- });
+ return std::equal(sortedA.begin(), sortedA.end(), sortedB.begin(),
+ [](const Symbol* a, const Symbol* b) -> bool {
+ return a->symbol.equals(&b->symbol) &&
+ a->value == b->value;
+ });
}
Attribute* Attribute::clone(StringPool* /*newPool*/) const {
- return new Attribute(*this);
+ return new Attribute(*this);
}
void Attribute::printMask(std::ostream* out) const {
- if (typeMask == android::ResTable_map::TYPE_ANY) {
- *out << "any";
- return;
- }
+ if (typeMask == android::ResTable_map::TYPE_ANY) {
+ *out << "any";
+ return;
+ }
- bool set = false;
- if ((typeMask & android::ResTable_map::TYPE_REFERENCE) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "reference";
+ bool set = false;
+ if ((typeMask & android::ResTable_map::TYPE_REFERENCE) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "reference";
+ }
- if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "string";
+ if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "string";
+ }
- if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "integer";
+ if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "integer";
+ }
- if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "boolean";
+ if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "boolean";
+ }
- if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "color";
+ if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "color";
+ }
- if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "float";
+ if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "float";
+ }
- if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "dimension";
+ if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "dimension";
+ }
- if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "fraction";
+ if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "fraction";
+ }
- if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "enum";
+ if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "enum";
+ }
- if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "flags";
+ if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
}
+ *out << "flags";
+ }
}
void Attribute::print(std::ostream* out) const {
- *out << "(attr) ";
- printMask(out);
+ *out << "(attr) ";
+ printMask(out);
- if (!symbols.empty()) {
- *out << " [" << util::joiner(symbols, ", ") << "]";
- }
+ if (!symbols.empty()) {
+ *out << " [" << util::joiner(symbols, ", ") << "]";
+ }
- if (minInt != std::numeric_limits<int32_t>::min()) {
- *out << " min=" << minInt;
- }
+ if (minInt != std::numeric_limits<int32_t>::min()) {
+ *out << " min=" << minInt;
+ }
- if (maxInt != std::numeric_limits<int32_t>::max()) {
- *out << " max=" << maxInt;
- }
+ if (maxInt != std::numeric_limits<int32_t>::max()) {
+ *out << " max=" << maxInt;
+ }
- if (isWeak()) {
- *out << " [weak]";
- }
+ if (isWeak()) {
+ *out << " [weak]";
+ }
}
-static void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr,
+static void buildAttributeMismatchMessage(DiagMessage* msg,
+ const Attribute* attr,
const Item* value) {
- *msg << "expected";
- if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
- *msg << " boolean";
- }
+ *msg << "expected";
+ if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+ *msg << " boolean";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
- *msg << " color";
- }
+ if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
+ *msg << " color";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
- *msg << " dimension";
- }
+ if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
+ *msg << " dimension";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
- *msg << " enum";
- }
+ if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
+ *msg << " enum";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
- *msg << " flags";
- }
+ if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
+ *msg << " flags";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
- *msg << " float";
- }
+ if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
+ *msg << " float";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
- *msg << " fraction";
- }
+ if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
+ *msg << " fraction";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
- *msg << " integer";
- }
+ if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
+ *msg << " integer";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
- *msg << " reference";
- }
+ if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
+ *msg << " reference";
+ }
- if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
- *msg << " string";
- }
+ if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
+ *msg << " string";
+ }
- *msg << " but got " << *value;
+ *msg << " but got " << *value;
}
bool Attribute::matches(const Item* item, DiagMessage* outMsg) const {
- android::Res_value val = {};
- item->flatten(&val);
+ android::Res_value val = {};
+ item->flatten(&val);
- // Always allow references.
- const uint32_t mask = typeMask | android::ResTable_map::TYPE_REFERENCE;
- if (!(mask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
- if (outMsg) {
- buildAttributeMismatchMessage(outMsg, this, item);
- }
- return false;
-
- } else if (ResourceUtils::androidTypeToAttributeTypeMask(val.dataType) &
- android::ResTable_map::TYPE_INTEGER) {
- if (static_cast<int32_t>(util::deviceToHost32(val.data)) < minInt) {
- if (outMsg) {
- *outMsg << *item << " is less than minimum integer " << minInt;
- }
- return false;
- } else if (static_cast<int32_t>(util::deviceToHost32(val.data)) > maxInt) {
- if (outMsg) {
- *outMsg << *item << " is greater than maximum integer " << maxInt;
- }
- return false;
- }
+ // Always allow references.
+ const uint32_t mask = typeMask | android::ResTable_map::TYPE_REFERENCE;
+ if (!(mask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
+ if (outMsg) {
+ buildAttributeMismatchMessage(outMsg, this, item);
}
- return true;
+ return false;
+
+ } else if (ResourceUtils::androidTypeToAttributeTypeMask(val.dataType) &
+ android::ResTable_map::TYPE_INTEGER) {
+ if (static_cast<int32_t>(util::deviceToHost32(val.data)) < minInt) {
+ if (outMsg) {
+ *outMsg << *item << " is less than minimum integer " << minInt;
+ }
+ return false;
+ } else if (static_cast<int32_t>(util::deviceToHost32(val.data)) > maxInt) {
+ if (outMsg) {
+ *outMsg << *item << " is greater than maximum integer " << maxInt;
+ }
+ return false;
+ }
+ }
+ return true;
}
bool Style::equals(const Value* value) const {
- const Style* other = valueCast<Style>(value);
- if (!other) {
- return false;
- }
- if (bool(parent) != bool(other->parent) ||
- (parent && other->parent && !parent.value().equals(&other->parent.value()))) {
- return false;
- }
+ const Style* other = valueCast<Style>(value);
+ if (!other) {
+ return false;
+ }
+ if (bool(parent) != bool(other->parent) ||
+ (parent && other->parent &&
+ !parent.value().equals(&other->parent.value()))) {
+ return false;
+ }
- if (entries.size() != other->entries.size()) {
- return false;
- }
+ if (entries.size() != other->entries.size()) {
+ return false;
+ }
- std::vector<const Entry*> sortedA;
- std::transform(entries.begin(), entries.end(),
- std::back_inserter(sortedA), addPointer<const Entry>);
- std::sort(sortedA.begin(), sortedA.end(), [](const Entry* a, const Entry* b) -> bool {
- return a->key.name < b->key.name;
- });
+ std::vector<const Entry*> sortedA;
+ std::transform(entries.begin(), entries.end(), std::back_inserter(sortedA),
+ addPointer<const Entry>);
+ std::sort(sortedA.begin(), sortedA.end(),
+ [](const Entry* a, const Entry* b) -> bool {
+ return a->key.name < b->key.name;
+ });
- std::vector<const Entry*> sortedB;
- std::transform(other->entries.begin(), other->entries.end(),
- std::back_inserter(sortedB), addPointer<const Entry>);
- std::sort(sortedB.begin(), sortedB.end(), [](const Entry* a, const Entry* b) -> bool {
- return a->key.name < b->key.name;
- });
+ std::vector<const Entry*> sortedB;
+ std::transform(other->entries.begin(), other->entries.end(),
+ std::back_inserter(sortedB), addPointer<const Entry>);
+ std::sort(sortedB.begin(), sortedB.end(),
+ [](const Entry* a, const Entry* b) -> bool {
+ return a->key.name < b->key.name;
+ });
- return std::equal(sortedA.begin(), sortedA.end(), sortedB.begin(),
- [](const Entry* a, const Entry* b) -> bool {
- return a->key.equals(&b->key) && a->value->equals(b->value.get());
- });
+ return std::equal(sortedA.begin(), sortedA.end(), sortedB.begin(),
+ [](const Entry* a, const Entry* b) -> bool {
+ return a->key.equals(&b->key) &&
+ a->value->equals(b->value.get());
+ });
}
Style* Style::clone(StringPool* newPool) const {
- Style* style = new Style();
- style->parent = parent;
- style->parentInferred = parentInferred;
- style->mComment = mComment;
- style->mSource = mSource;
- for (auto& entry : entries) {
- style->entries.push_back(Entry{
- entry.key,
- std::unique_ptr<Item>(entry.value->clone(newPool))
- });
- }
- return style;
+ Style* style = new Style();
+ style->parent = parent;
+ style->parentInferred = parentInferred;
+ style->mComment = mComment;
+ style->mSource = mSource;
+ for (auto& entry : entries) {
+ style->entries.push_back(
+ Entry{entry.key, std::unique_ptr<Item>(entry.value->clone(newPool))});
+ }
+ return style;
}
void Style::print(std::ostream* out) const {
- *out << "(style) ";
- if (parent && parent.value().name) {
- if (parent.value().privateReference) {
- *out << "*";
- }
- *out << parent.value().name.value();
+ *out << "(style) ";
+ if (parent && parent.value().name) {
+ if (parent.value().privateReference) {
+ *out << "*";
}
- *out << " ["
- << util::joiner(entries, ", ")
- << "]";
+ *out << parent.value().name.value();
+ }
+ *out << " [" << util::joiner(entries, ", ") << "]";
}
-static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value) {
- if (value.key.name) {
- out << value.key.name.value();
- } else if (value.key.id) {
- out << value.key.id.value();
- } else {
- out << "???";
- }
- out << " = ";
- value.value->print(&out);
- return out;
+static ::std::ostream& operator<<(::std::ostream& out,
+ const Style::Entry& value) {
+ if (value.key.name) {
+ out << value.key.name.value();
+ } else if (value.key.id) {
+ out << value.key.id.value();
+ } else {
+ out << "???";
+ }
+ out << " = ";
+ value.value->print(&out);
+ return out;
}
bool Array::equals(const Value* value) const {
- const Array* other = valueCast<Array>(value);
- if (!other) {
- return false;
- }
+ const Array* other = valueCast<Array>(value);
+ if (!other) {
+ return false;
+ }
- if (items.size() != other->items.size()) {
- return false;
- }
+ if (items.size() != other->items.size()) {
+ return false;
+ }
- return std::equal(items.begin(), items.end(), other->items.begin(),
- [](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool {
- return a->equals(b.get());
- });
+ return std::equal(items.begin(), items.end(), other->items.begin(),
+ [](const std::unique_ptr<Item>& a,
+ const std::unique_ptr<Item>& b) -> bool {
+ return a->equals(b.get());
+ });
}
Array* Array::clone(StringPool* newPool) const {
- Array* array = new Array();
- array->mComment = mComment;
- array->mSource = mSource;
- for (auto& item : items) {
- array->items.emplace_back(std::unique_ptr<Item>(item->clone(newPool)));
- }
- return array;
+ Array* array = new Array();
+ array->mComment = mComment;
+ array->mSource = mSource;
+ for (auto& item : items) {
+ array->items.emplace_back(std::unique_ptr<Item>(item->clone(newPool)));
+ }
+ return array;
}
void Array::print(std::ostream* out) const {
- *out << "(array) ["
- << util::joiner(items, ", ")
- << "]";
+ *out << "(array) [" << util::joiner(items, ", ") << "]";
}
bool Plural::equals(const Value* value) const {
- const Plural* other = valueCast<Plural>(value);
- if (!other) {
- return false;
- }
+ const Plural* other = valueCast<Plural>(value);
+ if (!other) {
+ return false;
+ }
- if (values.size() != other->values.size()) {
- return false;
- }
+ if (values.size() != other->values.size()) {
+ return false;
+ }
- return std::equal(values.begin(), values.end(), other->values.begin(),
- [](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool {
- if (bool(a) != bool(b)) {
- return false;
- }
- return bool(a) == bool(b) || a->equals(b.get());
- });
+ return std::equal(values.begin(), values.end(), other->values.begin(),
+ [](const std::unique_ptr<Item>& a,
+ const std::unique_ptr<Item>& b) -> bool {
+ if (bool(a) != bool(b)) {
+ return false;
+ }
+ return bool(a) == bool(b) || a->equals(b.get());
+ });
}
Plural* Plural::clone(StringPool* newPool) const {
- Plural* p = new Plural();
- p->mComment = mComment;
- p->mSource = mSource;
- const size_t count = values.size();
- for (size_t i = 0; i < count; i++) {
- if (values[i]) {
- p->values[i] = std::unique_ptr<Item>(values[i]->clone(newPool));
- }
+ Plural* p = new Plural();
+ p->mComment = mComment;
+ p->mSource = mSource;
+ const size_t count = values.size();
+ for (size_t i = 0; i < count; i++) {
+ if (values[i]) {
+ p->values[i] = std::unique_ptr<Item>(values[i]->clone(newPool));
}
- return p;
+ }
+ return p;
}
void Plural::print(std::ostream* out) const {
- *out << "(plural)";
- if (values[Zero]) {
- *out << " zero=" << *values[Zero];
- }
+ *out << "(plural)";
+ if (values[Zero]) {
+ *out << " zero=" << *values[Zero];
+ }
- if (values[One]) {
- *out << " one=" << *values[One];
- }
+ if (values[One]) {
+ *out << " one=" << *values[One];
+ }
- if (values[Two]) {
- *out << " two=" << *values[Two];
- }
+ if (values[Two]) {
+ *out << " two=" << *values[Two];
+ }
- if (values[Few]) {
- *out << " few=" << *values[Few];
- }
+ if (values[Few]) {
+ *out << " few=" << *values[Few];
+ }
- if (values[Many]) {
- *out << " many=" << *values[Many];
- }
+ if (values[Many]) {
+ *out << " many=" << *values[Many];
+ }
}
-static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) {
- return out << *item;
+static ::std::ostream& operator<<(::std::ostream& out,
+ const std::unique_ptr<Item>& item) {
+ return out << *item;
}
bool Styleable::equals(const Value* value) const {
- const Styleable* other = valueCast<Styleable>(value);
- if (!other) {
- return false;
- }
+ const Styleable* other = valueCast<Styleable>(value);
+ if (!other) {
+ return false;
+ }
- if (entries.size() != other->entries.size()) {
- return false;
- }
+ if (entries.size() != other->entries.size()) {
+ return false;
+ }
- return std::equal(entries.begin(), entries.end(), other->entries.begin(),
- [](const Reference& a, const Reference& b) -> bool {
- return a.equals(&b);
- });
+ return std::equal(entries.begin(), entries.end(), other->entries.begin(),
+ [](const Reference& a, const Reference& b) -> bool {
+ return a.equals(&b);
+ });
}
Styleable* Styleable::clone(StringPool* /*newPool*/) const {
- return new Styleable(*this);
+ return new Styleable(*this);
}
void Styleable::print(std::ostream* out) const {
- *out << "(styleable) " << " ["
- << util::joiner(entries, ", ")
- << "]";
+ *out << "(styleable) "
+ << " [" << util::joiner(entries, ", ") << "]";
}
bool operator<(const Reference& a, const Reference& b) {
- int cmp = a.name.valueOrDefault({}).compare(b.name.valueOrDefault({}));
- if (cmp != 0) return cmp < 0;
- return a.id < b.id;
+ int cmp = a.name.valueOrDefault({}).compare(b.name.valueOrDefault({}));
+ if (cmp != 0) return cmp < 0;
+ return a.id < b.id;
}
bool operator==(const Reference& a, const Reference& b) {
- return a.name == b.name && a.id == b.id;
+ return a.name == b.name && a.id == b.id;
}
bool operator!=(const Reference& a, const Reference& b) {
- return a.name != b.name || a.id != b.id;
+ return a.name != b.name || a.id != b.id;
}
struct NameOnlyComparator {
- bool operator()(const Reference& a, const Reference& b) const {
- return a.name < b.name;
- }
+ bool operator()(const Reference& a, const Reference& b) const {
+ return a.name < b.name;
+ }
};
void Styleable::mergeWith(Styleable* other) {
- // Compare only names, because some References may already have their IDs assigned
- // (framework IDs that don't change).
- std::set<Reference, NameOnlyComparator> references;
- references.insert(entries.begin(), entries.end());
- references.insert(other->entries.begin(), other->entries.end());
- entries.clear();
- entries.reserve(references.size());
- entries.insert(entries.end(), references.begin(), references.end());
+ // Compare only names, because some References may already have their IDs
+ // assigned
+ // (framework IDs that don't change).
+ std::set<Reference, NameOnlyComparator> references;
+ references.insert(entries.begin(), entries.end());
+ references.insert(other->entries.begin(), other->entries.end());
+ entries.clear();
+ entries.reserve(references.size());
+ entries.insert(entries.end(), references.begin(), references.end());
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 5e5d1f3..a28ffe5 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -23,8 +23,8 @@
#include "io/File.h"
#include "util/Maybe.h"
-#include <array>
#include <androidfw/ResourceTypes.h>
+#include <array>
#include <ostream>
#include <vector>
@@ -40,84 +40,64 @@
* but it is the simplest strategy.
*/
struct Value {
- virtual ~Value() = default;
+ virtual ~Value() = default;
- /**
- * Whether this value is weak and can be overridden without
- * warning or error. Default is false.
- */
- bool isWeak() const {
- return mWeak;
- }
+ /**
+ * Whether this value is weak and can be overridden without
+ * warning or error. Default is false.
+ */
+ bool isWeak() const { return mWeak; }
- void setWeak(bool val) {
- mWeak = val;
- }
+ void setWeak(bool val) { mWeak = val; }
- // Whether the value is marked as translateable.
- // This does not persist when flattened.
- // It is only used during compilation phase.
- void setTranslateable(bool val) {
- mTranslateable = val;
- }
+ // Whether the value is marked as translateable.
+ // This does not persist when flattened.
+ // It is only used during compilation phase.
+ void setTranslateable(bool val) { mTranslateable = val; }
- // Default true.
- bool isTranslateable() const {
- return mTranslateable;
- }
+ // Default true.
+ bool isTranslateable() const { return mTranslateable; }
- /**
- * Returns the source where this value was defined.
- */
- const Source& getSource() const {
- return mSource;
- }
+ /**
+ * Returns the source where this value was defined.
+ */
+ const Source& getSource() const { return mSource; }
- void setSource(const Source& source) {
- mSource = source;
- }
+ void setSource(const Source& source) { mSource = source; }
- void setSource(Source&& source) {
- mSource = std::move(source);
- }
+ void setSource(Source&& source) { mSource = std::move(source); }
- /**
- * Returns the comment that was associated with this resource.
- */
- const std::string& getComment() const {
- return mComment;
- }
+ /**
+ * Returns the comment that was associated with this resource.
+ */
+ const std::string& getComment() const { return mComment; }
- void setComment(const StringPiece& str) {
- mComment = str.toString();
- }
+ void setComment(const StringPiece& str) { mComment = str.toString(); }
- void setComment(std::string&& str) {
- mComment = std::move(str);
- }
+ void setComment(std::string&& str) { mComment = std::move(str); }
- virtual bool equals(const Value* value) const = 0;
+ virtual bool equals(const Value* value) const = 0;
- /**
- * Calls the appropriate overload of ValueVisitor.
- */
- virtual void accept(RawValueVisitor* visitor) = 0;
+ /**
+ * Calls the appropriate overload of ValueVisitor.
+ */
+ virtual void accept(RawValueVisitor* visitor) = 0;
- /**
- * Clone the value.
- */
- virtual Value* clone(StringPool* newPool) const = 0;
+ /**
+ * Clone the value.
+ */
+ virtual Value* clone(StringPool* newPool) const = 0;
- /**
- * Human readable printout of this value.
- */
- virtual void print(std::ostream* out) const = 0;
+ /**
+ * Human readable printout of this value.
+ */
+ virtual void print(std::ostream* out) const = 0;
-protected:
- Source mSource;
- std::string mComment;
- bool mWeak = false;
- bool mTranslateable = true;
+ protected:
+ Source mSource;
+ std::string mComment;
+ bool mWeak = false;
+ bool mTranslateable = true;
};
/**
@@ -125,23 +105,24 @@
*/
template <typename Derived>
struct BaseValue : public Value {
- void accept(RawValueVisitor* visitor) override;
+ void accept(RawValueVisitor* visitor) override;
};
/**
* A resource item with a single value. This maps to android::ResTable_entry.
*/
struct Item : public Value {
- /**
- * Clone the Item.
- */
- virtual Item* clone(StringPool* newPool) const override = 0;
+ /**
+ * Clone the Item.
+ */
+ virtual Item* clone(StringPool* newPool) const override = 0;
- /**
- * Fills in an android::Res_value structure with this Item's binary representation.
- * Returns false if an error occurred.
- */
- virtual bool flatten(android::Res_value* outValue) const = 0;
+ /**
+ * Fills in an android::Res_value structure with this Item's binary
+ * representation.
+ * Returns false if an error occurred.
+ */
+ virtual bool flatten(android::Res_value* outValue) const = 0;
};
/**
@@ -149,35 +130,37 @@
*/
template <typename Derived>
struct BaseItem : public Item {
- void accept(RawValueVisitor* visitor) override;
+ void accept(RawValueVisitor* visitor) override;
};
/**
- * A reference to another resource. This maps to android::Res_value::TYPE_REFERENCE.
+ * A reference to another resource. This maps to
+ * android::Res_value::TYPE_REFERENCE.
*
- * A reference can be symbolic (with the name set to a valid resource name) or be
+ * A reference can be symbolic (with the name set to a valid resource name) or
+ * be
* numeric (the id is set to a valid resource ID).
*/
struct Reference : public BaseItem<Reference> {
- enum class Type {
- kResource,
- kAttribute,
- };
+ enum class Type {
+ kResource,
+ kAttribute,
+ };
- Maybe<ResourceName> name;
- Maybe<ResourceId> id;
- Reference::Type referenceType;
- bool privateReference = false;
+ Maybe<ResourceName> name;
+ Maybe<ResourceId> id;
+ Reference::Type referenceType;
+ bool privateReference = false;
- Reference();
- explicit Reference(const ResourceNameRef& n, Type type = Type::kResource);
- explicit Reference(const ResourceId& i, Type type = Type::kResource);
- explicit Reference(const ResourceNameRef& n, const ResourceId& i);
+ Reference();
+ explicit Reference(const ResourceNameRef& n, Type type = Type::kResource);
+ explicit Reference(const ResourceId& i, Type type = Type::kResource);
+ explicit Reference(const ResourceNameRef& n, const ResourceId& i);
- bool equals(const Value* value) const override;
- bool flatten(android::Res_value* outValue) const override;
- Reference* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool equals(const Value* value) const override;
+ bool flatten(android::Res_value* outValue) const override;
+ Reference* clone(StringPool* newPool) const override;
+ void print(std::ostream* out) const override;
};
bool operator<(const Reference&, const Reference&);
@@ -187,11 +170,11 @@
* An ID resource. Has no real value, just a place holder.
*/
struct Id : public BaseItem<Id> {
- Id() { mWeak = true; }
- bool equals(const Value* value) const override;
- bool flatten(android::Res_value* out) const override;
- Id* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ Id() { mWeak = true; }
+ bool equals(const Value* value) const override;
+ bool flatten(android::Res_value* out) const override;
+ Id* clone(StringPool* newPool) const override;
+ void print(std::ostream* out) const override;
};
/**
@@ -200,164 +183,157 @@
* end up in the final resource table.
*/
struct RawString : public BaseItem<RawString> {
- StringPool::Ref value;
+ StringPool::Ref value;
- explicit RawString(const StringPool::Ref& ref);
+ explicit RawString(const StringPool::Ref& ref);
- bool equals(const Value* value) const override;
- bool flatten(android::Res_value* outValue) const override;
- RawString* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool equals(const Value* value) const override;
+ bool flatten(android::Res_value* outValue) const override;
+ RawString* clone(StringPool* newPool) const override;
+ void print(std::ostream* out) const override;
};
struct String : public BaseItem<String> {
- StringPool::Ref value;
+ StringPool::Ref value;
- explicit String(const StringPool::Ref& ref);
+ explicit String(const StringPool::Ref& ref);
- bool equals(const Value* value) const override;
- bool flatten(android::Res_value* outValue) const override;
- String* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool equals(const Value* value) const override;
+ bool flatten(android::Res_value* outValue) const override;
+ String* clone(StringPool* newPool) const override;
+ void print(std::ostream* out) const override;
};
struct StyledString : public BaseItem<StyledString> {
- StringPool::StyleRef value;
+ StringPool::StyleRef value;
- explicit StyledString(const StringPool::StyleRef& ref);
+ explicit StyledString(const StringPool::StyleRef& ref);
- bool equals(const Value* value) const override;
- bool flatten(android::Res_value* outValue) const override;
- StyledString* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool equals(const Value* value) const override;
+ bool flatten(android::Res_value* outValue) const override;
+ StyledString* clone(StringPool* newPool) const override;
+ void print(std::ostream* out) const override;
};
struct FileReference : public BaseItem<FileReference> {
- StringPool::Ref path;
+ StringPool::Ref path;
- /**
- * A handle to the file object from which this file can be read.
- */
- io::IFile* file = nullptr;
+ /**
+ * A handle to the file object from which this file can be read.
+ */
+ io::IFile* file = nullptr;
- FileReference() = default;
- explicit FileReference(const StringPool::Ref& path);
+ FileReference() = default;
+ explicit FileReference(const StringPool::Ref& path);
- bool equals(const Value* value) const override;
- bool flatten(android::Res_value* outValue) const override;
- FileReference* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool equals(const Value* value) const override;
+ bool flatten(android::Res_value* outValue) const override;
+ FileReference* clone(StringPool* newPool) const override;
+ void print(std::ostream* out) const override;
};
/**
* Represents any other android::Res_value.
*/
struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
- android::Res_value value;
+ android::Res_value value;
- BinaryPrimitive() = default;
- explicit BinaryPrimitive(const android::Res_value& val);
- BinaryPrimitive(uint8_t dataType, uint32_t data);
+ BinaryPrimitive() = default;
+ explicit BinaryPrimitive(const android::Res_value& val);
+ BinaryPrimitive(uint8_t dataType, uint32_t data);
- bool equals(const Value* value) const override;
- bool flatten(android::Res_value* outValue) const override;
- BinaryPrimitive* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool equals(const Value* value) const override;
+ bool flatten(android::Res_value* outValue) const override;
+ BinaryPrimitive* clone(StringPool* newPool) const override;
+ void print(std::ostream* out) const override;
};
struct Attribute : public BaseValue<Attribute> {
- struct Symbol {
- Reference symbol;
- uint32_t value;
- };
+ struct Symbol {
+ Reference symbol;
+ uint32_t value;
+ };
- uint32_t typeMask;
- int32_t minInt;
- int32_t maxInt;
- std::vector<Symbol> symbols;
+ uint32_t typeMask;
+ int32_t minInt;
+ int32_t maxInt;
+ std::vector<Symbol> symbols;
- explicit Attribute(bool w, uint32_t t = 0u);
+ explicit Attribute(bool w, uint32_t t = 0u);
- bool equals(const Value* value) const override;
- Attribute* clone(StringPool* newPool) const override;
- void printMask(std::ostream* out) const;
- void print(std::ostream* out) const override;
- bool matches(const Item* item, DiagMessage* outMsg) const;
+ bool equals(const Value* value) const override;
+ Attribute* clone(StringPool* newPool) const override;
+ void printMask(std::ostream* out) const;
+ void print(std::ostream* out) const override;
+ bool matches(const Item* item, DiagMessage* outMsg) const;
};
struct Style : public BaseValue<Style> {
- struct Entry {
- Reference key;
- std::unique_ptr<Item> value;
- };
+ struct Entry {
+ Reference key;
+ std::unique_ptr<Item> value;
+ };
- Maybe<Reference> parent;
+ Maybe<Reference> parent;
- /**
- * If set to true, the parent was auto inferred from the
- * style's name.
- */
- bool parentInferred = false;
+ /**
+ * If set to true, the parent was auto inferred from the
+ * style's name.
+ */
+ bool parentInferred = false;
- std::vector<Entry> entries;
+ std::vector<Entry> entries;
- bool equals(const Value* value) const override;
- Style* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool equals(const Value* value) const override;
+ Style* clone(StringPool* newPool) const override;
+ void print(std::ostream* out) const override;
};
struct Array : public BaseValue<Array> {
- std::vector<std::unique_ptr<Item>> items;
+ std::vector<std::unique_ptr<Item>> items;
- bool equals(const Value* value) const override;
- Array* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool equals(const Value* value) const override;
+ Array* clone(StringPool* newPool) const override;
+ void print(std::ostream* out) const override;
};
struct Plural : public BaseValue<Plural> {
- enum {
- Zero = 0,
- One,
- Two,
- Few,
- Many,
- Other,
- Count
- };
+ enum { Zero = 0, One, Two, Few, Many, Other, Count };
- std::array<std::unique_ptr<Item>, Count> values;
+ std::array<std::unique_ptr<Item>, Count> values;
- bool equals(const Value* value) const override;
- Plural* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
+ bool equals(const Value* value) const override;
+ Plural* clone(StringPool* newPool) const override;
+ void print(std::ostream* out) const override;
};
struct Styleable : public BaseValue<Styleable> {
- std::vector<Reference> entries;
+ std::vector<Reference> entries;
- bool equals(const Value* value) const override;
- Styleable* clone(StringPool* newPool) const override;
- void print(std::ostream* out) const override;
- void mergeWith(Styleable* styleable);
+ bool equals(const Value* value) const override;
+ Styleable* clone(StringPool* newPool) const override;
+ void print(std::ostream* out) const override;
+ void mergeWith(Styleable* styleable);
};
/**
* Stream operator for printing Value objects.
*/
inline ::std::ostream& operator<<(::std::ostream& out, const Value& value) {
- value.print(&out);
- return out;
+ value.print(&out);
+ return out;
}
-inline ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) {
- if (s.symbol.name) {
- out << s.symbol.name.value().entry;
- } else {
- out << "???";
- }
- return out << "=" << s.value;
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const Attribute::Symbol& s) {
+ if (s.symbol.name) {
+ out << s.symbol.name.value().entry;
+ } else {
+ out << "???";
+ }
+ return out << "=" << s.value;
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_RESOURCE_VALUES_H
+#endif // AAPT_RESOURCE_VALUES_H
diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp
index 06cddc7..4b6b122 100644
--- a/tools/aapt2/Resource_test.cpp
+++ b/tools/aapt2/Resource_test.cpp
@@ -20,96 +20,96 @@
namespace aapt {
TEST(ResourceTypeTest, ParseResourceTypes) {
- const ResourceType* type = parseResourceType("anim");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kAnim);
+ const ResourceType* type = parseResourceType("anim");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kAnim);
- type = parseResourceType("animator");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kAnimator);
+ type = parseResourceType("animator");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kAnimator);
- type = parseResourceType("array");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kArray);
+ type = parseResourceType("array");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kArray);
- type = parseResourceType("attr");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kAttr);
+ type = parseResourceType("attr");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kAttr);
- type = parseResourceType("^attr-private");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kAttrPrivate);
+ type = parseResourceType("^attr-private");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kAttrPrivate);
- type = parseResourceType("bool");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kBool);
+ type = parseResourceType("bool");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kBool);
- type = parseResourceType("color");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kColor);
+ type = parseResourceType("color");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kColor);
- type = parseResourceType("dimen");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kDimen);
+ type = parseResourceType("dimen");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kDimen);
- type = parseResourceType("drawable");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kDrawable);
+ type = parseResourceType("drawable");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kDrawable);
- type = parseResourceType("fraction");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kFraction);
+ type = parseResourceType("fraction");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kFraction);
- type = parseResourceType("id");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kId);
+ type = parseResourceType("id");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kId);
- type = parseResourceType("integer");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kInteger);
+ type = parseResourceType("integer");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kInteger);
- type = parseResourceType("interpolator");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kInterpolator);
+ type = parseResourceType("interpolator");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kInterpolator);
- type = parseResourceType("layout");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kLayout);
+ type = parseResourceType("layout");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kLayout);
- type = parseResourceType("menu");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kMenu);
+ type = parseResourceType("menu");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kMenu);
- type = parseResourceType("mipmap");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kMipmap);
+ type = parseResourceType("mipmap");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kMipmap);
- type = parseResourceType("plurals");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kPlurals);
+ type = parseResourceType("plurals");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kPlurals);
- type = parseResourceType("raw");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kRaw);
+ type = parseResourceType("raw");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kRaw);
- type = parseResourceType("string");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kString);
+ type = parseResourceType("string");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kString);
- type = parseResourceType("style");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kStyle);
+ type = parseResourceType("style");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kStyle);
- type = parseResourceType("transition");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kTransition);
+ type = parseResourceType("transition");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kTransition);
- type = parseResourceType("xml");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kXml);
+ type = parseResourceType("xml");
+ ASSERT_NE(type, nullptr);
+ EXPECT_EQ(*type, ResourceType::kXml);
- type = parseResourceType("blahaha");
- EXPECT_EQ(type, nullptr);
+ type = parseResourceType("blahaha");
+ EXPECT_EQ(type, nullptr);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index ccf0383..75375da 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -27,719 +27,721 @@
static int sDevelopmentSdkLevel = 26;
static const std::vector<std::pair<uint16_t, size_t>> sAttrIdMap = {
- { 0x021c, 1 },
- { 0x021d, 2 },
- { 0x0269, SDK_CUPCAKE },
- { 0x028d, SDK_DONUT },
- { 0x02ad, SDK_ECLAIR },
- { 0x02b3, SDK_ECLAIR_0_1 },
- { 0x02b5, SDK_ECLAIR_MR1 },
- { 0x02bd, SDK_FROYO },
- { 0x02cb, SDK_GINGERBREAD },
- { 0x0361, SDK_HONEYCOMB },
- { 0x0363, SDK_HONEYCOMB_MR1 },
- { 0x0366, SDK_HONEYCOMB_MR2 },
- { 0x03a6, SDK_ICE_CREAM_SANDWICH },
- { 0x03ae, SDK_JELLY_BEAN },
- { 0x03cc, SDK_JELLY_BEAN_MR1 },
- { 0x03da, SDK_JELLY_BEAN_MR2 },
- { 0x03f1, SDK_KITKAT },
- { 0x03f6, SDK_KITKAT_WATCH },
- { 0x04ce, SDK_LOLLIPOP },
+ {0x021c, 1},
+ {0x021d, 2},
+ {0x0269, SDK_CUPCAKE},
+ {0x028d, SDK_DONUT},
+ {0x02ad, SDK_ECLAIR},
+ {0x02b3, SDK_ECLAIR_0_1},
+ {0x02b5, SDK_ECLAIR_MR1},
+ {0x02bd, SDK_FROYO},
+ {0x02cb, SDK_GINGERBREAD},
+ {0x0361, SDK_HONEYCOMB},
+ {0x0363, SDK_HONEYCOMB_MR1},
+ {0x0366, SDK_HONEYCOMB_MR2},
+ {0x03a6, SDK_ICE_CREAM_SANDWICH},
+ {0x03ae, SDK_JELLY_BEAN},
+ {0x03cc, SDK_JELLY_BEAN_MR1},
+ {0x03da, SDK_JELLY_BEAN_MR2},
+ {0x03f1, SDK_KITKAT},
+ {0x03f6, SDK_KITKAT_WATCH},
+ {0x04ce, SDK_LOLLIPOP},
};
-static bool lessEntryId(const std::pair<uint16_t, size_t>& p, uint16_t entryId) {
- return p.first < entryId;
+static bool lessEntryId(const std::pair<uint16_t, size_t>& p,
+ uint16_t entryId) {
+ return p.first < entryId;
}
size_t findAttributeSdkLevel(const ResourceId& id) {
- if (id.packageId() != 0x01 && id.typeId() != 0x01) {
- return 0;
- }
- auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(), id.entryId(), lessEntryId);
- if (iter == sAttrIdMap.end()) {
- return SDK_LOLLIPOP_MR1;
- }
- return iter->second;
+ if (id.packageId() != 0x01 && id.typeId() != 0x01) {
+ return 0;
+ }
+ auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(),
+ id.entryId(), lessEntryId);
+ if (iter == sAttrIdMap.end()) {
+ return SDK_LOLLIPOP_MR1;
+ }
+ return iter->second;
}
static const std::unordered_map<std::string, size_t> sAttrMap = {
- { "marqueeRepeatLimit", 2 },
- { "windowNoDisplay", 3 },
- { "backgroundDimEnabled", 3 },
- { "inputType", 3 },
- { "isDefault", 3 },
- { "windowDisablePreview", 3 },
- { "privateImeOptions", 3 },
- { "editorExtras", 3 },
- { "settingsActivity", 3 },
- { "fastScrollEnabled", 3 },
- { "reqTouchScreen", 3 },
- { "reqKeyboardType", 3 },
- { "reqHardKeyboard", 3 },
- { "reqNavigation", 3 },
- { "windowSoftInputMode", 3 },
- { "imeFullscreenBackground", 3 },
- { "noHistory", 3 },
- { "headerDividersEnabled", 3 },
- { "footerDividersEnabled", 3 },
- { "candidatesTextStyleSpans", 3 },
- { "smoothScrollbar", 3 },
- { "reqFiveWayNav", 3 },
- { "keyBackground", 3 },
- { "keyTextSize", 3 },
- { "labelTextSize", 3 },
- { "keyTextColor", 3 },
- { "keyPreviewLayout", 3 },
- { "keyPreviewOffset", 3 },
- { "keyPreviewHeight", 3 },
- { "verticalCorrection", 3 },
- { "popupLayout", 3 },
- { "state_long_pressable", 3 },
- { "keyWidth", 3 },
- { "keyHeight", 3 },
- { "horizontalGap", 3 },
- { "verticalGap", 3 },
- { "rowEdgeFlags", 3 },
- { "codes", 3 },
- { "popupKeyboard", 3 },
- { "popupCharacters", 3 },
- { "keyEdgeFlags", 3 },
- { "isModifier", 3 },
- { "isSticky", 3 },
- { "isRepeatable", 3 },
- { "iconPreview", 3 },
- { "keyOutputText", 3 },
- { "keyLabel", 3 },
- { "keyIcon", 3 },
- { "keyboardMode", 3 },
- { "isScrollContainer", 3 },
- { "fillEnabled", 3 },
- { "updatePeriodMillis", 3 },
- { "initialLayout", 3 },
- { "voiceSearchMode", 3 },
- { "voiceLanguageModel", 3 },
- { "voicePromptText", 3 },
- { "voiceLanguage", 3 },
- { "voiceMaxResults", 3 },
- { "bottomOffset", 3 },
- { "topOffset", 3 },
- { "allowSingleTap", 3 },
- { "handle", 3 },
- { "content", 3 },
- { "animateOnClick", 3 },
- { "configure", 3 },
- { "hapticFeedbackEnabled", 3 },
- { "innerRadius", 3 },
- { "thickness", 3 },
- { "sharedUserLabel", 3 },
- { "dropDownWidth", 3 },
- { "dropDownAnchor", 3 },
- { "imeOptions", 3 },
- { "imeActionLabel", 3 },
- { "imeActionId", 3 },
- { "imeExtractEnterAnimation", 3 },
- { "imeExtractExitAnimation", 3 },
- { "tension", 4 },
- { "extraTension", 4 },
- { "anyDensity", 4 },
- { "searchSuggestThreshold", 4 },
- { "includeInGlobalSearch", 4 },
- { "onClick", 4 },
- { "targetSdkVersion", 4 },
- { "maxSdkVersion", 4 },
- { "testOnly", 4 },
- { "contentDescription", 4 },
- { "gestureStrokeWidth", 4 },
- { "gestureColor", 4 },
- { "uncertainGestureColor", 4 },
- { "fadeOffset", 4 },
- { "fadeDuration", 4 },
- { "gestureStrokeType", 4 },
- { "gestureStrokeLengthThreshold", 4 },
- { "gestureStrokeSquarenessThreshold", 4 },
- { "gestureStrokeAngleThreshold", 4 },
- { "eventsInterceptionEnabled", 4 },
- { "fadeEnabled", 4 },
- { "backupAgent", 4 },
- { "allowBackup", 4 },
- { "glEsVersion", 4 },
- { "queryAfterZeroResults", 4 },
- { "dropDownHeight", 4 },
- { "smallScreens", 4 },
- { "normalScreens", 4 },
- { "largeScreens", 4 },
- { "progressBarStyleInverse", 4 },
- { "progressBarStyleSmallInverse", 4 },
- { "progressBarStyleLargeInverse", 4 },
- { "searchSettingsDescription", 4 },
- { "textColorPrimaryInverseDisableOnly", 4 },
- { "autoUrlDetect", 4 },
- { "resizeable", 4 },
- { "required", 5 },
- { "accountType", 5 },
- { "contentAuthority", 5 },
- { "userVisible", 5 },
- { "windowShowWallpaper", 5 },
- { "wallpaperOpenEnterAnimation", 5 },
- { "wallpaperOpenExitAnimation", 5 },
- { "wallpaperCloseEnterAnimation", 5 },
- { "wallpaperCloseExitAnimation", 5 },
- { "wallpaperIntraOpenEnterAnimation", 5 },
- { "wallpaperIntraOpenExitAnimation", 5 },
- { "wallpaperIntraCloseEnterAnimation", 5 },
- { "wallpaperIntraCloseExitAnimation", 5 },
- { "supportsUploading", 5 },
- { "killAfterRestore", 5 },
- { "restoreNeedsApplication", 5 },
- { "smallIcon", 5 },
- { "accountPreferences", 5 },
- { "textAppearanceSearchResultSubtitle", 5 },
- { "textAppearanceSearchResultTitle", 5 },
- { "summaryColumn", 5 },
- { "detailColumn", 5 },
- { "detailSocialSummary", 5 },
- { "thumbnail", 5 },
- { "detachWallpaper", 5 },
- { "finishOnCloseSystemDialogs", 5 },
- { "scrollbarFadeDuration", 5 },
- { "scrollbarDefaultDelayBeforeFade", 5 },
- { "fadeScrollbars", 5 },
- { "colorBackgroundCacheHint", 5 },
- { "dropDownHorizontalOffset", 5 },
- { "dropDownVerticalOffset", 5 },
- { "quickContactBadgeStyleWindowSmall", 6 },
- { "quickContactBadgeStyleWindowMedium", 6 },
- { "quickContactBadgeStyleWindowLarge", 6 },
- { "quickContactBadgeStyleSmallWindowSmall", 6 },
- { "quickContactBadgeStyleSmallWindowMedium", 6 },
- { "quickContactBadgeStyleSmallWindowLarge", 6 },
- { "author", 7 },
- { "autoStart", 7 },
- { "expandableListViewWhiteStyle", 8 },
- { "installLocation", 8 },
- { "vmSafeMode", 8 },
- { "webTextViewStyle", 8 },
- { "restoreAnyVersion", 8 },
- { "tabStripLeft", 8 },
- { "tabStripRight", 8 },
- { "tabStripEnabled", 8 },
- { "logo", 9 },
- { "xlargeScreens", 9 },
- { "immersive", 9 },
- { "overScrollMode", 9 },
- { "overScrollHeader", 9 },
- { "overScrollFooter", 9 },
- { "filterTouchesWhenObscured", 9 },
- { "textSelectHandleLeft", 9 },
- { "textSelectHandleRight", 9 },
- { "textSelectHandle", 9 },
- { "textSelectHandleWindowStyle", 9 },
- { "popupAnimationStyle", 9 },
- { "screenSize", 9 },
- { "screenDensity", 9 },
- { "allContactsName", 11 },
- { "windowActionBar", 11 },
- { "actionBarStyle", 11 },
- { "navigationMode", 11 },
- { "displayOptions", 11 },
- { "subtitle", 11 },
- { "customNavigationLayout", 11 },
- { "hardwareAccelerated", 11 },
- { "measureWithLargestChild", 11 },
- { "animateFirstView", 11 },
- { "dropDownSpinnerStyle", 11 },
- { "actionDropDownStyle", 11 },
- { "actionButtonStyle", 11 },
- { "showAsAction", 11 },
- { "previewImage", 11 },
- { "actionModeBackground", 11 },
- { "actionModeCloseDrawable", 11 },
- { "windowActionModeOverlay", 11 },
- { "valueFrom", 11 },
- { "valueTo", 11 },
- { "valueType", 11 },
- { "propertyName", 11 },
- { "ordering", 11 },
- { "fragment", 11 },
- { "windowActionBarOverlay", 11 },
- { "fragmentOpenEnterAnimation", 11 },
- { "fragmentOpenExitAnimation", 11 },
- { "fragmentCloseEnterAnimation", 11 },
- { "fragmentCloseExitAnimation", 11 },
- { "fragmentFadeEnterAnimation", 11 },
- { "fragmentFadeExitAnimation", 11 },
- { "actionBarSize", 11 },
- { "imeSubtypeLocale", 11 },
- { "imeSubtypeMode", 11 },
- { "imeSubtypeExtraValue", 11 },
- { "splitMotionEvents", 11 },
- { "listChoiceBackgroundIndicator", 11 },
- { "spinnerMode", 11 },
- { "animateLayoutChanges", 11 },
- { "actionBarTabStyle", 11 },
- { "actionBarTabBarStyle", 11 },
- { "actionBarTabTextStyle", 11 },
- { "actionOverflowButtonStyle", 11 },
- { "actionModeCloseButtonStyle", 11 },
- { "titleTextStyle", 11 },
- { "subtitleTextStyle", 11 },
- { "iconifiedByDefault", 11 },
- { "actionLayout", 11 },
- { "actionViewClass", 11 },
- { "activatedBackgroundIndicator", 11 },
- { "state_activated", 11 },
- { "listPopupWindowStyle", 11 },
- { "popupMenuStyle", 11 },
- { "textAppearanceLargePopupMen", 11 },
- { "textAppearanceSmallPopupMen", 11 },
- { "breadCrumbTitle", 11 },
- { "breadCrumbShortTitle", 11 },
- { "listDividerAlertDialog", 11 },
- { "textColorAlertDialogListItem", 11 },
- { "loopViews", 11 },
- { "dialogTheme", 11 },
- { "alertDialogTheme", 11 },
- { "dividerVertical", 11 },
- { "homeAsUpIndicator", 11 },
- { "enterFadeDuration", 11 },
- { "exitFadeDuration", 11 },
- { "selectableItemBackground", 11 },
- { "autoAdvanceViewId", 11 },
- { "useIntrinsicSizeAsMinimum", 11 },
- { "actionModeCutDrawable", 11 },
- { "actionModeCopyDrawable", 11 },
- { "actionModePasteDrawable", 11 },
- { "textEditPasteWindowLayout", 11 },
- { "textEditNoPasteWindowLayout", 11 },
- { "textIsSelectable", 11 },
- { "windowEnableSplitTouch", 11 },
- { "indeterminateProgressStyle", 11 },
- { "progressBarPadding", 11 },
- { "animationResolution", 11 },
- { "state_accelerated", 11 },
- { "baseline", 11 },
- { "homeLayout", 11 },
- { "opacity", 11 },
- { "alpha", 11 },
- { "transformPivotX", 11 },
- { "transformPivotY", 11 },
- { "translationX", 11 },
- { "translationY", 11 },
- { "scaleX", 11 },
- { "scaleY", 11 },
- { "rotation", 11 },
- { "rotationX", 11 },
- { "rotationY", 11 },
- { "showDividers", 11 },
- { "dividerPadding", 11 },
- { "borderlessButtonStyle", 11 },
- { "dividerHorizontal", 11 },
- { "itemPadding", 11 },
- { "buttonBarStyle", 11 },
- { "buttonBarButtonStyle", 11 },
- { "segmentedButtonStyle", 11 },
- { "staticWallpaperPreview", 11 },
- { "allowParallelSyncs", 11 },
- { "isAlwaysSyncable", 11 },
- { "verticalScrollbarPosition", 11 },
- { "fastScrollAlwaysVisible", 11 },
- { "fastScrollThumbDrawable", 11 },
- { "fastScrollPreviewBackgroundLeft", 11 },
- { "fastScrollPreviewBackgroundRight", 11 },
- { "fastScrollTrackDrawable", 11 },
- { "fastScrollOverlayPosition", 11 },
- { "customTokens", 11 },
- { "nextFocusForward", 11 },
- { "firstDayOfWeek", 11 },
- { "showWeekNumber", 11 },
- { "minDate", 11 },
- { "maxDate", 11 },
- { "shownWeekCount", 11 },
- { "selectedWeekBackgroundColor", 11 },
- { "focusedMonthDateColor", 11 },
- { "unfocusedMonthDateColor", 11 },
- { "weekNumberColor", 11 },
- { "weekSeparatorLineColor", 11 },
- { "selectedDateVerticalBar", 11 },
- { "weekDayTextAppearance", 11 },
- { "dateTextAppearance", 11 },
- { "solidColor", 11 },
- { "spinnersShown", 11 },
- { "calendarViewShown", 11 },
- { "state_multiline", 11 },
- { "detailsElementBackground", 11 },
- { "textColorHighlightInverse", 11 },
- { "textColorLinkInverse", 11 },
- { "editTextColor", 11 },
- { "editTextBackground", 11 },
- { "horizontalScrollViewStyle", 11 },
- { "layerType", 11 },
- { "alertDialogIcon", 11 },
- { "windowMinWidthMajor", 11 },
- { "windowMinWidthMinor", 11 },
- { "queryHint", 11 },
- { "fastScrollTextColor", 11 },
- { "largeHeap", 11 },
- { "windowCloseOnTouchOutside", 11 },
- { "datePickerStyle", 11 },
- { "calendarViewStyle", 11 },
- { "textEditSidePasteWindowLayout", 11 },
- { "textEditSideNoPasteWindowLayout", 11 },
- { "actionMenuTextAppearance", 11 },
- { "actionMenuTextColor", 11 },
- { "textCursorDrawable", 12 },
- { "resizeMode", 12 },
- { "requiresSmallestWidthDp", 12 },
- { "compatibleWidthLimitDp", 12 },
- { "largestWidthLimitDp", 12 },
- { "state_hovered", 13 },
- { "state_drag_can_accept", 13 },
- { "state_drag_hovered", 13 },
- { "stopWithTask", 13 },
- { "switchTextOn", 13 },
- { "switchTextOff", 13 },
- { "switchPreferenceStyle", 13 },
- { "switchTextAppearance", 13 },
- { "track", 13 },
- { "switchMinWidth", 13 },
- { "switchPadding", 13 },
- { "thumbTextPadding", 13 },
- { "textSuggestionsWindowStyle", 13 },
- { "textEditSuggestionItemLayout", 13 },
- { "rowCount", 13 },
- { "rowOrderPreserved", 13 },
- { "columnCount", 13 },
- { "columnOrderPreserved", 13 },
- { "useDefaultMargins", 13 },
- { "alignmentMode", 13 },
- { "layout_row", 13 },
- { "layout_rowSpan", 13 },
- { "layout_columnSpan", 13 },
- { "actionModeSelectAllDrawable", 13 },
- { "isAuxiliary", 13 },
- { "accessibilityEventTypes", 13 },
- { "packageNames", 13 },
- { "accessibilityFeedbackType", 13 },
- { "notificationTimeout", 13 },
- { "accessibilityFlags", 13 },
- { "canRetrieveWindowContent", 13 },
- { "listPreferredItemHeightLarge", 13 },
- { "listPreferredItemHeightSmall", 13 },
- { "actionBarSplitStyle", 13 },
- { "actionProviderClass", 13 },
- { "backgroundStacked", 13 },
- { "backgroundSplit", 13 },
- { "textAllCaps", 13 },
- { "colorPressedHighlight", 13 },
- { "colorLongPressedHighlight", 13 },
- { "colorFocusedHighlight", 13 },
- { "colorActivatedHighlight", 13 },
- { "colorMultiSelectHighlight", 13 },
- { "drawableStart", 13 },
- { "drawableEnd", 13 },
- { "actionModeStyle", 13 },
- { "minResizeWidth", 13 },
- { "minResizeHeight", 13 },
- { "actionBarWidgetTheme", 13 },
- { "uiOptions", 13 },
- { "subtypeLocale", 13 },
- { "subtypeExtraValue", 13 },
- { "actionBarDivider", 13 },
- { "actionBarItemBackground", 13 },
- { "actionModeSplitBackground", 13 },
- { "textAppearanceListItem", 13 },
- { "textAppearanceListItemSmall", 13 },
- { "targetDescriptions", 13 },
- { "directionDescriptions", 13 },
- { "overridesImplicitlyEnabledSubtype", 13 },
- { "listPreferredItemPaddingLeft", 13 },
- { "listPreferredItemPaddingRight", 13 },
- { "requiresFadingEdge", 13 },
- { "publicKey", 13 },
- { "parentActivityName", 16 },
- { "isolatedProcess", 16 },
- { "importantForAccessibility", 16 },
- { "keyboardLayout", 16 },
- { "fontFamily", 16 },
- { "mediaRouteButtonStyle", 16 },
- { "mediaRouteTypes", 16 },
- { "supportsRtl", 17 },
- { "textDirection", 17 },
- { "textAlignment", 17 },
- { "layoutDirection", 17 },
- { "paddingStart", 17 },
- { "paddingEnd", 17 },
- { "layout_marginStart", 17 },
- { "layout_marginEnd", 17 },
- { "layout_toStartOf", 17 },
- { "layout_toEndOf", 17 },
- { "layout_alignStart", 17 },
- { "layout_alignEnd", 17 },
- { "layout_alignParentStart", 17 },
- { "layout_alignParentEnd", 17 },
- { "listPreferredItemPaddingStart", 17 },
- { "listPreferredItemPaddingEnd", 17 },
- { "singleUser", 17 },
- { "presentationTheme", 17 },
- { "subtypeId", 17 },
- { "initialKeyguardLayout", 17 },
- { "widgetCategory", 17 },
- { "permissionGroupFlags", 17 },
- { "labelFor", 17 },
- { "permissionFlags", 17 },
- { "checkedTextViewStyle", 17 },
- { "showOnLockScreen", 17 },
- { "format12Hour", 17 },
- { "format24Hour", 17 },
- { "timeZone", 17 },
- { "mipMap", 18 },
- { "mirrorForRtl", 18 },
- { "windowOverscan", 18 },
- { "requiredForAllUsers", 18 },
- { "indicatorStart", 18 },
- { "indicatorEnd", 18 },
- { "childIndicatorStart", 18 },
- { "childIndicatorEnd", 18 },
- { "restrictedAccountType", 18 },
- { "requiredAccountType", 18 },
- { "canRequestTouchExplorationMode", 18 },
- { "canRequestEnhancedWebAccessibility", 18 },
- { "canRequestFilterKeyEvents", 18 },
- { "layoutMode", 18 },
- { "keySet", 19 },
- { "targetId", 19 },
- { "fromScene", 19 },
- { "toScene", 19 },
- { "transition", 19 },
- { "transitionOrdering", 19 },
- { "fadingMode", 19 },
- { "startDelay", 19 },
- { "ssp", 19 },
- { "sspPrefix", 19 },
- { "sspPattern", 19 },
- { "addPrintersActivity", 19 },
- { "vendor", 19 },
- { "category", 19 },
- { "isAsciiCapable", 19 },
- { "autoMirrored", 19 },
- { "supportsSwitchingToNextInputMethod", 19 },
- { "requireDeviceUnlock", 19 },
- { "apduServiceBanner", 19 },
- { "accessibilityLiveRegion", 19 },
- { "windowTranslucentStatus", 19 },
- { "windowTranslucentNavigation", 19 },
- { "advancedPrintOptionsActivity", 19 },
- { "banner", 20 },
- { "windowSwipeToDismiss", 20 },
- { "isGame", 20 },
- { "allowEmbedded", 20 },
- { "setupActivity", 20 },
- { "fastScrollStyle", 21 },
- { "windowContentTransitions", 21 },
- { "windowContentTransitionManager", 21 },
- { "translationZ", 21 },
- { "tintMode", 21 },
- { "controlX1", 21 },
- { "controlY1", 21 },
- { "controlX2", 21 },
- { "controlY2", 21 },
- { "transitionName", 21 },
- { "transitionGroup", 21 },
- { "viewportWidth", 21 },
- { "viewportHeight", 21 },
- { "fillColor", 21 },
- { "pathData", 21 },
- { "strokeColor", 21 },
- { "strokeWidth", 21 },
- { "trimPathStart", 21 },
- { "trimPathEnd", 21 },
- { "trimPathOffset", 21 },
- { "strokeLineCap", 21 },
- { "strokeLineJoin", 21 },
- { "strokeMiterLimit", 21 },
- { "colorControlNormal", 21 },
- { "colorControlActivated", 21 },
- { "colorButtonNormal", 21 },
- { "colorControlHighlight", 21 },
- { "persistableMode", 21 },
- { "titleTextAppearance", 21 },
- { "subtitleTextAppearance", 21 },
- { "slideEdge", 21 },
- { "actionBarTheme", 21 },
- { "textAppearanceListItemSecondary", 21 },
- { "colorPrimary", 21 },
- { "colorPrimaryDark", 21 },
- { "colorAccent", 21 },
- { "nestedScrollingEnabled", 21 },
- { "windowEnterTransition", 21 },
- { "windowExitTransition", 21 },
- { "windowSharedElementEnterTransition", 21 },
- { "windowSharedElementExitTransition", 21 },
- { "windowAllowReturnTransitionOverlap", 21 },
- { "windowAllowEnterTransitionOverlap", 21 },
- { "sessionService", 21 },
- { "stackViewStyle", 21 },
- { "switchStyle", 21 },
- { "elevation", 21 },
- { "excludeId", 21 },
- { "excludeClass", 21 },
- { "hideOnContentScroll", 21 },
- { "actionOverflowMenuStyle", 21 },
- { "documentLaunchMode", 21 },
- { "maxRecents", 21 },
- { "autoRemoveFromRecents", 21 },
- { "stateListAnimator", 21 },
- { "toId", 21 },
- { "fromId", 21 },
- { "reversible", 21 },
- { "splitTrack", 21 },
- { "targetName", 21 },
- { "excludeName", 21 },
- { "matchOrder", 21 },
- { "windowDrawsSystemBarBackgrounds", 21 },
- { "statusBarColor", 21 },
- { "navigationBarColor", 21 },
- { "contentInsetStart", 21 },
- { "contentInsetEnd", 21 },
- { "contentInsetLeft", 21 },
- { "contentInsetRight", 21 },
- { "paddingMode", 21 },
- { "layout_rowWeight", 21 },
- { "layout_columnWeight", 21 },
- { "translateX", 21 },
- { "translateY", 21 },
- { "selectableItemBackgroundBorderless", 21 },
- { "elegantTextHeight", 21 },
- { "searchKeyphraseId", 21 },
- { "searchKeyphrase", 21 },
- { "searchKeyphraseSupportedLocales", 21 },
- { "windowTransitionBackgroundFadeDuration", 21 },
- { "overlapAnchor", 21 },
- { "progressTint", 21 },
- { "progressTintMode", 21 },
- { "progressBackgroundTint", 21 },
- { "progressBackgroundTintMode", 21 },
- { "secondaryProgressTint", 21 },
- { "secondaryProgressTintMode", 21 },
- { "indeterminateTint", 21 },
- { "indeterminateTintMode", 21 },
- { "backgroundTint", 21 },
- { "backgroundTintMode", 21 },
- { "foregroundTint", 21 },
- { "foregroundTintMode", 21 },
- { "buttonTint", 21 },
- { "buttonTintMode", 21 },
- { "thumbTint", 21 },
- { "thumbTintMode", 21 },
- { "fullBackupOnly", 21 },
- { "propertyXName", 21 },
- { "propertyYName", 21 },
- { "relinquishTaskIdentity", 21 },
- { "tileModeX", 21 },
- { "tileModeY", 21 },
- { "actionModeShareDrawable", 21 },
- { "actionModeFindDrawable", 21 },
- { "actionModeWebSearchDrawable", 21 },
- { "transitionVisibilityMode", 21 },
- { "minimumHorizontalAngle", 21 },
- { "minimumVerticalAngle", 21 },
- { "maximumAngle", 21 },
- { "searchViewStyle", 21 },
- { "closeIcon", 21 },
- { "goIcon", 21 },
- { "searchIcon", 21 },
- { "voiceIcon", 21 },
- { "commitIcon", 21 },
- { "suggestionRowLayout", 21 },
- { "queryBackground", 21 },
- { "submitBackground", 21 },
- { "buttonBarPositiveButtonStyle", 21 },
- { "buttonBarNeutralButtonStyle", 21 },
- { "buttonBarNegativeButtonStyle", 21 },
- { "popupElevation", 21 },
- { "actionBarPopupTheme", 21 },
- { "multiArch", 21 },
- { "touchscreenBlocksFocus", 21 },
- { "windowElevation", 21 },
- { "launchTaskBehindTargetAnimation", 21 },
- { "launchTaskBehindSourceAnimation", 21 },
- { "restrictionType", 21 },
- { "dayOfWeekBackground", 21 },
- { "dayOfWeekTextAppearance", 21 },
- { "headerMonthTextAppearance", 21 },
- { "headerDayOfMonthTextAppearance", 21 },
- { "headerYearTextAppearance", 21 },
- { "yearListItemTextAppearance", 21 },
- { "yearListSelectorColor", 21 },
- { "calendarTextColor", 21 },
- { "recognitionService", 21 },
- { "timePickerStyle", 21 },
- { "timePickerDialogTheme", 21 },
- { "headerTimeTextAppearance", 21 },
- { "headerAmPmTextAppearance", 21 },
- { "numbersTextColor", 21 },
- { "numbersBackgroundColor", 21 },
- { "numbersSelectorColor", 21 },
- { "amPmTextColor", 21 },
- { "amPmBackgroundColor", 21 },
- { "searchKeyphraseRecognitionFlags", 21 },
- { "checkMarkTint", 21 },
- { "checkMarkTintMode", 21 },
- { "popupTheme", 21 },
- { "toolbarStyle", 21 },
- { "windowClipToOutline", 21 },
- { "datePickerDialogTheme", 21 },
- { "showText", 21 },
- { "windowReturnTransition", 21 },
- { "windowReenterTransition", 21 },
- { "windowSharedElementReturnTransition", 21 },
- { "windowSharedElementReenterTransition", 21 },
- { "resumeWhilePausing", 21 },
- { "datePickerMode", 21 },
- { "timePickerMode", 21 },
- { "inset", 21 },
- { "letterSpacing", 21 },
- { "fontFeatureSettings", 21 },
- { "outlineProvider", 21 },
- { "contentAgeHint", 21 },
- { "country", 21 },
- { "windowSharedElementsUseOverlay", 21 },
- { "reparent", 21 },
- { "reparentWithOverlay", 21 },
- { "ambientShadowAlpha", 21 },
- { "spotShadowAlpha", 21 },
- { "navigationIcon", 21 },
- { "navigationContentDescription", 21 },
- { "fragmentExitTransition", 21 },
- { "fragmentEnterTransition", 21 },
- { "fragmentSharedElementEnterTransition", 21 },
- { "fragmentReturnTransition", 21 },
- { "fragmentSharedElementReturnTransition", 21 },
- { "fragmentReenterTransition", 21 },
- { "fragmentAllowEnterTransitionOverlap", 21 },
- { "fragmentAllowReturnTransitionOverlap", 21 },
- { "patternPathData", 21 },
- { "strokeAlpha", 21 },
- { "fillAlpha", 21 },
- { "windowActivityTransitions", 21 },
- { "colorEdgeEffect", 21 }
-};
+ {"marqueeRepeatLimit", 2},
+ {"windowNoDisplay", 3},
+ {"backgroundDimEnabled", 3},
+ {"inputType", 3},
+ {"isDefault", 3},
+ {"windowDisablePreview", 3},
+ {"privateImeOptions", 3},
+ {"editorExtras", 3},
+ {"settingsActivity", 3},
+ {"fastScrollEnabled", 3},
+ {"reqTouchScreen", 3},
+ {"reqKeyboardType", 3},
+ {"reqHardKeyboard", 3},
+ {"reqNavigation", 3},
+ {"windowSoftInputMode", 3},
+ {"imeFullscreenBackground", 3},
+ {"noHistory", 3},
+ {"headerDividersEnabled", 3},
+ {"footerDividersEnabled", 3},
+ {"candidatesTextStyleSpans", 3},
+ {"smoothScrollbar", 3},
+ {"reqFiveWayNav", 3},
+ {"keyBackground", 3},
+ {"keyTextSize", 3},
+ {"labelTextSize", 3},
+ {"keyTextColor", 3},
+ {"keyPreviewLayout", 3},
+ {"keyPreviewOffset", 3},
+ {"keyPreviewHeight", 3},
+ {"verticalCorrection", 3},
+ {"popupLayout", 3},
+ {"state_long_pressable", 3},
+ {"keyWidth", 3},
+ {"keyHeight", 3},
+ {"horizontalGap", 3},
+ {"verticalGap", 3},
+ {"rowEdgeFlags", 3},
+ {"codes", 3},
+ {"popupKeyboard", 3},
+ {"popupCharacters", 3},
+ {"keyEdgeFlags", 3},
+ {"isModifier", 3},
+ {"isSticky", 3},
+ {"isRepeatable", 3},
+ {"iconPreview", 3},
+ {"keyOutputText", 3},
+ {"keyLabel", 3},
+ {"keyIcon", 3},
+ {"keyboardMode", 3},
+ {"isScrollContainer", 3},
+ {"fillEnabled", 3},
+ {"updatePeriodMillis", 3},
+ {"initialLayout", 3},
+ {"voiceSearchMode", 3},
+ {"voiceLanguageModel", 3},
+ {"voicePromptText", 3},
+ {"voiceLanguage", 3},
+ {"voiceMaxResults", 3},
+ {"bottomOffset", 3},
+ {"topOffset", 3},
+ {"allowSingleTap", 3},
+ {"handle", 3},
+ {"content", 3},
+ {"animateOnClick", 3},
+ {"configure", 3},
+ {"hapticFeedbackEnabled", 3},
+ {"innerRadius", 3},
+ {"thickness", 3},
+ {"sharedUserLabel", 3},
+ {"dropDownWidth", 3},
+ {"dropDownAnchor", 3},
+ {"imeOptions", 3},
+ {"imeActionLabel", 3},
+ {"imeActionId", 3},
+ {"imeExtractEnterAnimation", 3},
+ {"imeExtractExitAnimation", 3},
+ {"tension", 4},
+ {"extraTension", 4},
+ {"anyDensity", 4},
+ {"searchSuggestThreshold", 4},
+ {"includeInGlobalSearch", 4},
+ {"onClick", 4},
+ {"targetSdkVersion", 4},
+ {"maxSdkVersion", 4},
+ {"testOnly", 4},
+ {"contentDescription", 4},
+ {"gestureStrokeWidth", 4},
+ {"gestureColor", 4},
+ {"uncertainGestureColor", 4},
+ {"fadeOffset", 4},
+ {"fadeDuration", 4},
+ {"gestureStrokeType", 4},
+ {"gestureStrokeLengthThreshold", 4},
+ {"gestureStrokeSquarenessThreshold", 4},
+ {"gestureStrokeAngleThreshold", 4},
+ {"eventsInterceptionEnabled", 4},
+ {"fadeEnabled", 4},
+ {"backupAgent", 4},
+ {"allowBackup", 4},
+ {"glEsVersion", 4},
+ {"queryAfterZeroResults", 4},
+ {"dropDownHeight", 4},
+ {"smallScreens", 4},
+ {"normalScreens", 4},
+ {"largeScreens", 4},
+ {"progressBarStyleInverse", 4},
+ {"progressBarStyleSmallInverse", 4},
+ {"progressBarStyleLargeInverse", 4},
+ {"searchSettingsDescription", 4},
+ {"textColorPrimaryInverseDisableOnly", 4},
+ {"autoUrlDetect", 4},
+ {"resizeable", 4},
+ {"required", 5},
+ {"accountType", 5},
+ {"contentAuthority", 5},
+ {"userVisible", 5},
+ {"windowShowWallpaper", 5},
+ {"wallpaperOpenEnterAnimation", 5},
+ {"wallpaperOpenExitAnimation", 5},
+ {"wallpaperCloseEnterAnimation", 5},
+ {"wallpaperCloseExitAnimation", 5},
+ {"wallpaperIntraOpenEnterAnimation", 5},
+ {"wallpaperIntraOpenExitAnimation", 5},
+ {"wallpaperIntraCloseEnterAnimation", 5},
+ {"wallpaperIntraCloseExitAnimation", 5},
+ {"supportsUploading", 5},
+ {"killAfterRestore", 5},
+ {"restoreNeedsApplication", 5},
+ {"smallIcon", 5},
+ {"accountPreferences", 5},
+ {"textAppearanceSearchResultSubtitle", 5},
+ {"textAppearanceSearchResultTitle", 5},
+ {"summaryColumn", 5},
+ {"detailColumn", 5},
+ {"detailSocialSummary", 5},
+ {"thumbnail", 5},
+ {"detachWallpaper", 5},
+ {"finishOnCloseSystemDialogs", 5},
+ {"scrollbarFadeDuration", 5},
+ {"scrollbarDefaultDelayBeforeFade", 5},
+ {"fadeScrollbars", 5},
+ {"colorBackgroundCacheHint", 5},
+ {"dropDownHorizontalOffset", 5},
+ {"dropDownVerticalOffset", 5},
+ {"quickContactBadgeStyleWindowSmall", 6},
+ {"quickContactBadgeStyleWindowMedium", 6},
+ {"quickContactBadgeStyleWindowLarge", 6},
+ {"quickContactBadgeStyleSmallWindowSmall", 6},
+ {"quickContactBadgeStyleSmallWindowMedium", 6},
+ {"quickContactBadgeStyleSmallWindowLarge", 6},
+ {"author", 7},
+ {"autoStart", 7},
+ {"expandableListViewWhiteStyle", 8},
+ {"installLocation", 8},
+ {"vmSafeMode", 8},
+ {"webTextViewStyle", 8},
+ {"restoreAnyVersion", 8},
+ {"tabStripLeft", 8},
+ {"tabStripRight", 8},
+ {"tabStripEnabled", 8},
+ {"logo", 9},
+ {"xlargeScreens", 9},
+ {"immersive", 9},
+ {"overScrollMode", 9},
+ {"overScrollHeader", 9},
+ {"overScrollFooter", 9},
+ {"filterTouchesWhenObscured", 9},
+ {"textSelectHandleLeft", 9},
+ {"textSelectHandleRight", 9},
+ {"textSelectHandle", 9},
+ {"textSelectHandleWindowStyle", 9},
+ {"popupAnimationStyle", 9},
+ {"screenSize", 9},
+ {"screenDensity", 9},
+ {"allContactsName", 11},
+ {"windowActionBar", 11},
+ {"actionBarStyle", 11},
+ {"navigationMode", 11},
+ {"displayOptions", 11},
+ {"subtitle", 11},
+ {"customNavigationLayout", 11},
+ {"hardwareAccelerated", 11},
+ {"measureWithLargestChild", 11},
+ {"animateFirstView", 11},
+ {"dropDownSpinnerStyle", 11},
+ {"actionDropDownStyle", 11},
+ {"actionButtonStyle", 11},
+ {"showAsAction", 11},
+ {"previewImage", 11},
+ {"actionModeBackground", 11},
+ {"actionModeCloseDrawable", 11},
+ {"windowActionModeOverlay", 11},
+ {"valueFrom", 11},
+ {"valueTo", 11},
+ {"valueType", 11},
+ {"propertyName", 11},
+ {"ordering", 11},
+ {"fragment", 11},
+ {"windowActionBarOverlay", 11},
+ {"fragmentOpenEnterAnimation", 11},
+ {"fragmentOpenExitAnimation", 11},
+ {"fragmentCloseEnterAnimation", 11},
+ {"fragmentCloseExitAnimation", 11},
+ {"fragmentFadeEnterAnimation", 11},
+ {"fragmentFadeExitAnimation", 11},
+ {"actionBarSize", 11},
+ {"imeSubtypeLocale", 11},
+ {"imeSubtypeMode", 11},
+ {"imeSubtypeExtraValue", 11},
+ {"splitMotionEvents", 11},
+ {"listChoiceBackgroundIndicator", 11},
+ {"spinnerMode", 11},
+ {"animateLayoutChanges", 11},
+ {"actionBarTabStyle", 11},
+ {"actionBarTabBarStyle", 11},
+ {"actionBarTabTextStyle", 11},
+ {"actionOverflowButtonStyle", 11},
+ {"actionModeCloseButtonStyle", 11},
+ {"titleTextStyle", 11},
+ {"subtitleTextStyle", 11},
+ {"iconifiedByDefault", 11},
+ {"actionLayout", 11},
+ {"actionViewClass", 11},
+ {"activatedBackgroundIndicator", 11},
+ {"state_activated", 11},
+ {"listPopupWindowStyle", 11},
+ {"popupMenuStyle", 11},
+ {"textAppearanceLargePopupMen", 11},
+ {"textAppearanceSmallPopupMen", 11},
+ {"breadCrumbTitle", 11},
+ {"breadCrumbShortTitle", 11},
+ {"listDividerAlertDialog", 11},
+ {"textColorAlertDialogListItem", 11},
+ {"loopViews", 11},
+ {"dialogTheme", 11},
+ {"alertDialogTheme", 11},
+ {"dividerVertical", 11},
+ {"homeAsUpIndicator", 11},
+ {"enterFadeDuration", 11},
+ {"exitFadeDuration", 11},
+ {"selectableItemBackground", 11},
+ {"autoAdvanceViewId", 11},
+ {"useIntrinsicSizeAsMinimum", 11},
+ {"actionModeCutDrawable", 11},
+ {"actionModeCopyDrawable", 11},
+ {"actionModePasteDrawable", 11},
+ {"textEditPasteWindowLayout", 11},
+ {"textEditNoPasteWindowLayout", 11},
+ {"textIsSelectable", 11},
+ {"windowEnableSplitTouch", 11},
+ {"indeterminateProgressStyle", 11},
+ {"progressBarPadding", 11},
+ {"animationResolution", 11},
+ {"state_accelerated", 11},
+ {"baseline", 11},
+ {"homeLayout", 11},
+ {"opacity", 11},
+ {"alpha", 11},
+ {"transformPivotX", 11},
+ {"transformPivotY", 11},
+ {"translationX", 11},
+ {"translationY", 11},
+ {"scaleX", 11},
+ {"scaleY", 11},
+ {"rotation", 11},
+ {"rotationX", 11},
+ {"rotationY", 11},
+ {"showDividers", 11},
+ {"dividerPadding", 11},
+ {"borderlessButtonStyle", 11},
+ {"dividerHorizontal", 11},
+ {"itemPadding", 11},
+ {"buttonBarStyle", 11},
+ {"buttonBarButtonStyle", 11},
+ {"segmentedButtonStyle", 11},
+ {"staticWallpaperPreview", 11},
+ {"allowParallelSyncs", 11},
+ {"isAlwaysSyncable", 11},
+ {"verticalScrollbarPosition", 11},
+ {"fastScrollAlwaysVisible", 11},
+ {"fastScrollThumbDrawable", 11},
+ {"fastScrollPreviewBackgroundLeft", 11},
+ {"fastScrollPreviewBackgroundRight", 11},
+ {"fastScrollTrackDrawable", 11},
+ {"fastScrollOverlayPosition", 11},
+ {"customTokens", 11},
+ {"nextFocusForward", 11},
+ {"firstDayOfWeek", 11},
+ {"showWeekNumber", 11},
+ {"minDate", 11},
+ {"maxDate", 11},
+ {"shownWeekCount", 11},
+ {"selectedWeekBackgroundColor", 11},
+ {"focusedMonthDateColor", 11},
+ {"unfocusedMonthDateColor", 11},
+ {"weekNumberColor", 11},
+ {"weekSeparatorLineColor", 11},
+ {"selectedDateVerticalBar", 11},
+ {"weekDayTextAppearance", 11},
+ {"dateTextAppearance", 11},
+ {"solidColor", 11},
+ {"spinnersShown", 11},
+ {"calendarViewShown", 11},
+ {"state_multiline", 11},
+ {"detailsElementBackground", 11},
+ {"textColorHighlightInverse", 11},
+ {"textColorLinkInverse", 11},
+ {"editTextColor", 11},
+ {"editTextBackground", 11},
+ {"horizontalScrollViewStyle", 11},
+ {"layerType", 11},
+ {"alertDialogIcon", 11},
+ {"windowMinWidthMajor", 11},
+ {"windowMinWidthMinor", 11},
+ {"queryHint", 11},
+ {"fastScrollTextColor", 11},
+ {"largeHeap", 11},
+ {"windowCloseOnTouchOutside", 11},
+ {"datePickerStyle", 11},
+ {"calendarViewStyle", 11},
+ {"textEditSidePasteWindowLayout", 11},
+ {"textEditSideNoPasteWindowLayout", 11},
+ {"actionMenuTextAppearance", 11},
+ {"actionMenuTextColor", 11},
+ {"textCursorDrawable", 12},
+ {"resizeMode", 12},
+ {"requiresSmallestWidthDp", 12},
+ {"compatibleWidthLimitDp", 12},
+ {"largestWidthLimitDp", 12},
+ {"state_hovered", 13},
+ {"state_drag_can_accept", 13},
+ {"state_drag_hovered", 13},
+ {"stopWithTask", 13},
+ {"switchTextOn", 13},
+ {"switchTextOff", 13},
+ {"switchPreferenceStyle", 13},
+ {"switchTextAppearance", 13},
+ {"track", 13},
+ {"switchMinWidth", 13},
+ {"switchPadding", 13},
+ {"thumbTextPadding", 13},
+ {"textSuggestionsWindowStyle", 13},
+ {"textEditSuggestionItemLayout", 13},
+ {"rowCount", 13},
+ {"rowOrderPreserved", 13},
+ {"columnCount", 13},
+ {"columnOrderPreserved", 13},
+ {"useDefaultMargins", 13},
+ {"alignmentMode", 13},
+ {"layout_row", 13},
+ {"layout_rowSpan", 13},
+ {"layout_columnSpan", 13},
+ {"actionModeSelectAllDrawable", 13},
+ {"isAuxiliary", 13},
+ {"accessibilityEventTypes", 13},
+ {"packageNames", 13},
+ {"accessibilityFeedbackType", 13},
+ {"notificationTimeout", 13},
+ {"accessibilityFlags", 13},
+ {"canRetrieveWindowContent", 13},
+ {"listPreferredItemHeightLarge", 13},
+ {"listPreferredItemHeightSmall", 13},
+ {"actionBarSplitStyle", 13},
+ {"actionProviderClass", 13},
+ {"backgroundStacked", 13},
+ {"backgroundSplit", 13},
+ {"textAllCaps", 13},
+ {"colorPressedHighlight", 13},
+ {"colorLongPressedHighlight", 13},
+ {"colorFocusedHighlight", 13},
+ {"colorActivatedHighlight", 13},
+ {"colorMultiSelectHighlight", 13},
+ {"drawableStart", 13},
+ {"drawableEnd", 13},
+ {"actionModeStyle", 13},
+ {"minResizeWidth", 13},
+ {"minResizeHeight", 13},
+ {"actionBarWidgetTheme", 13},
+ {"uiOptions", 13},
+ {"subtypeLocale", 13},
+ {"subtypeExtraValue", 13},
+ {"actionBarDivider", 13},
+ {"actionBarItemBackground", 13},
+ {"actionModeSplitBackground", 13},
+ {"textAppearanceListItem", 13},
+ {"textAppearanceListItemSmall", 13},
+ {"targetDescriptions", 13},
+ {"directionDescriptions", 13},
+ {"overridesImplicitlyEnabledSubtype", 13},
+ {"listPreferredItemPaddingLeft", 13},
+ {"listPreferredItemPaddingRight", 13},
+ {"requiresFadingEdge", 13},
+ {"publicKey", 13},
+ {"parentActivityName", 16},
+ {"isolatedProcess", 16},
+ {"importantForAccessibility", 16},
+ {"keyboardLayout", 16},
+ {"fontFamily", 16},
+ {"mediaRouteButtonStyle", 16},
+ {"mediaRouteTypes", 16},
+ {"supportsRtl", 17},
+ {"textDirection", 17},
+ {"textAlignment", 17},
+ {"layoutDirection", 17},
+ {"paddingStart", 17},
+ {"paddingEnd", 17},
+ {"layout_marginStart", 17},
+ {"layout_marginEnd", 17},
+ {"layout_toStartOf", 17},
+ {"layout_toEndOf", 17},
+ {"layout_alignStart", 17},
+ {"layout_alignEnd", 17},
+ {"layout_alignParentStart", 17},
+ {"layout_alignParentEnd", 17},
+ {"listPreferredItemPaddingStart", 17},
+ {"listPreferredItemPaddingEnd", 17},
+ {"singleUser", 17},
+ {"presentationTheme", 17},
+ {"subtypeId", 17},
+ {"initialKeyguardLayout", 17},
+ {"widgetCategory", 17},
+ {"permissionGroupFlags", 17},
+ {"labelFor", 17},
+ {"permissionFlags", 17},
+ {"checkedTextViewStyle", 17},
+ {"showOnLockScreen", 17},
+ {"format12Hour", 17},
+ {"format24Hour", 17},
+ {"timeZone", 17},
+ {"mipMap", 18},
+ {"mirrorForRtl", 18},
+ {"windowOverscan", 18},
+ {"requiredForAllUsers", 18},
+ {"indicatorStart", 18},
+ {"indicatorEnd", 18},
+ {"childIndicatorStart", 18},
+ {"childIndicatorEnd", 18},
+ {"restrictedAccountType", 18},
+ {"requiredAccountType", 18},
+ {"canRequestTouchExplorationMode", 18},
+ {"canRequestEnhancedWebAccessibility", 18},
+ {"canRequestFilterKeyEvents", 18},
+ {"layoutMode", 18},
+ {"keySet", 19},
+ {"targetId", 19},
+ {"fromScene", 19},
+ {"toScene", 19},
+ {"transition", 19},
+ {"transitionOrdering", 19},
+ {"fadingMode", 19},
+ {"startDelay", 19},
+ {"ssp", 19},
+ {"sspPrefix", 19},
+ {"sspPattern", 19},
+ {"addPrintersActivity", 19},
+ {"vendor", 19},
+ {"category", 19},
+ {"isAsciiCapable", 19},
+ {"autoMirrored", 19},
+ {"supportsSwitchingToNextInputMethod", 19},
+ {"requireDeviceUnlock", 19},
+ {"apduServiceBanner", 19},
+ {"accessibilityLiveRegion", 19},
+ {"windowTranslucentStatus", 19},
+ {"windowTranslucentNavigation", 19},
+ {"advancedPrintOptionsActivity", 19},
+ {"banner", 20},
+ {"windowSwipeToDismiss", 20},
+ {"isGame", 20},
+ {"allowEmbedded", 20},
+ {"setupActivity", 20},
+ {"fastScrollStyle", 21},
+ {"windowContentTransitions", 21},
+ {"windowContentTransitionManager", 21},
+ {"translationZ", 21},
+ {"tintMode", 21},
+ {"controlX1", 21},
+ {"controlY1", 21},
+ {"controlX2", 21},
+ {"controlY2", 21},
+ {"transitionName", 21},
+ {"transitionGroup", 21},
+ {"viewportWidth", 21},
+ {"viewportHeight", 21},
+ {"fillColor", 21},
+ {"pathData", 21},
+ {"strokeColor", 21},
+ {"strokeWidth", 21},
+ {"trimPathStart", 21},
+ {"trimPathEnd", 21},
+ {"trimPathOffset", 21},
+ {"strokeLineCap", 21},
+ {"strokeLineJoin", 21},
+ {"strokeMiterLimit", 21},
+ {"colorControlNormal", 21},
+ {"colorControlActivated", 21},
+ {"colorButtonNormal", 21},
+ {"colorControlHighlight", 21},
+ {"persistableMode", 21},
+ {"titleTextAppearance", 21},
+ {"subtitleTextAppearance", 21},
+ {"slideEdge", 21},
+ {"actionBarTheme", 21},
+ {"textAppearanceListItemSecondary", 21},
+ {"colorPrimary", 21},
+ {"colorPrimaryDark", 21},
+ {"colorAccent", 21},
+ {"nestedScrollingEnabled", 21},
+ {"windowEnterTransition", 21},
+ {"windowExitTransition", 21},
+ {"windowSharedElementEnterTransition", 21},
+ {"windowSharedElementExitTransition", 21},
+ {"windowAllowReturnTransitionOverlap", 21},
+ {"windowAllowEnterTransitionOverlap", 21},
+ {"sessionService", 21},
+ {"stackViewStyle", 21},
+ {"switchStyle", 21},
+ {"elevation", 21},
+ {"excludeId", 21},
+ {"excludeClass", 21},
+ {"hideOnContentScroll", 21},
+ {"actionOverflowMenuStyle", 21},
+ {"documentLaunchMode", 21},
+ {"maxRecents", 21},
+ {"autoRemoveFromRecents", 21},
+ {"stateListAnimator", 21},
+ {"toId", 21},
+ {"fromId", 21},
+ {"reversible", 21},
+ {"splitTrack", 21},
+ {"targetName", 21},
+ {"excludeName", 21},
+ {"matchOrder", 21},
+ {"windowDrawsSystemBarBackgrounds", 21},
+ {"statusBarColor", 21},
+ {"navigationBarColor", 21},
+ {"contentInsetStart", 21},
+ {"contentInsetEnd", 21},
+ {"contentInsetLeft", 21},
+ {"contentInsetRight", 21},
+ {"paddingMode", 21},
+ {"layout_rowWeight", 21},
+ {"layout_columnWeight", 21},
+ {"translateX", 21},
+ {"translateY", 21},
+ {"selectableItemBackgroundBorderless", 21},
+ {"elegantTextHeight", 21},
+ {"searchKeyphraseId", 21},
+ {"searchKeyphrase", 21},
+ {"searchKeyphraseSupportedLocales", 21},
+ {"windowTransitionBackgroundFadeDuration", 21},
+ {"overlapAnchor", 21},
+ {"progressTint", 21},
+ {"progressTintMode", 21},
+ {"progressBackgroundTint", 21},
+ {"progressBackgroundTintMode", 21},
+ {"secondaryProgressTint", 21},
+ {"secondaryProgressTintMode", 21},
+ {"indeterminateTint", 21},
+ {"indeterminateTintMode", 21},
+ {"backgroundTint", 21},
+ {"backgroundTintMode", 21},
+ {"foregroundTint", 21},
+ {"foregroundTintMode", 21},
+ {"buttonTint", 21},
+ {"buttonTintMode", 21},
+ {"thumbTint", 21},
+ {"thumbTintMode", 21},
+ {"fullBackupOnly", 21},
+ {"propertyXName", 21},
+ {"propertyYName", 21},
+ {"relinquishTaskIdentity", 21},
+ {"tileModeX", 21},
+ {"tileModeY", 21},
+ {"actionModeShareDrawable", 21},
+ {"actionModeFindDrawable", 21},
+ {"actionModeWebSearchDrawable", 21},
+ {"transitionVisibilityMode", 21},
+ {"minimumHorizontalAngle", 21},
+ {"minimumVerticalAngle", 21},
+ {"maximumAngle", 21},
+ {"searchViewStyle", 21},
+ {"closeIcon", 21},
+ {"goIcon", 21},
+ {"searchIcon", 21},
+ {"voiceIcon", 21},
+ {"commitIcon", 21},
+ {"suggestionRowLayout", 21},
+ {"queryBackground", 21},
+ {"submitBackground", 21},
+ {"buttonBarPositiveButtonStyle", 21},
+ {"buttonBarNeutralButtonStyle", 21},
+ {"buttonBarNegativeButtonStyle", 21},
+ {"popupElevation", 21},
+ {"actionBarPopupTheme", 21},
+ {"multiArch", 21},
+ {"touchscreenBlocksFocus", 21},
+ {"windowElevation", 21},
+ {"launchTaskBehindTargetAnimation", 21},
+ {"launchTaskBehindSourceAnimation", 21},
+ {"restrictionType", 21},
+ {"dayOfWeekBackground", 21},
+ {"dayOfWeekTextAppearance", 21},
+ {"headerMonthTextAppearance", 21},
+ {"headerDayOfMonthTextAppearance", 21},
+ {"headerYearTextAppearance", 21},
+ {"yearListItemTextAppearance", 21},
+ {"yearListSelectorColor", 21},
+ {"calendarTextColor", 21},
+ {"recognitionService", 21},
+ {"timePickerStyle", 21},
+ {"timePickerDialogTheme", 21},
+ {"headerTimeTextAppearance", 21},
+ {"headerAmPmTextAppearance", 21},
+ {"numbersTextColor", 21},
+ {"numbersBackgroundColor", 21},
+ {"numbersSelectorColor", 21},
+ {"amPmTextColor", 21},
+ {"amPmBackgroundColor", 21},
+ {"searchKeyphraseRecognitionFlags", 21},
+ {"checkMarkTint", 21},
+ {"checkMarkTintMode", 21},
+ {"popupTheme", 21},
+ {"toolbarStyle", 21},
+ {"windowClipToOutline", 21},
+ {"datePickerDialogTheme", 21},
+ {"showText", 21},
+ {"windowReturnTransition", 21},
+ {"windowReenterTransition", 21},
+ {"windowSharedElementReturnTransition", 21},
+ {"windowSharedElementReenterTransition", 21},
+ {"resumeWhilePausing", 21},
+ {"datePickerMode", 21},
+ {"timePickerMode", 21},
+ {"inset", 21},
+ {"letterSpacing", 21},
+ {"fontFeatureSettings", 21},
+ {"outlineProvider", 21},
+ {"contentAgeHint", 21},
+ {"country", 21},
+ {"windowSharedElementsUseOverlay", 21},
+ {"reparent", 21},
+ {"reparentWithOverlay", 21},
+ {"ambientShadowAlpha", 21},
+ {"spotShadowAlpha", 21},
+ {"navigationIcon", 21},
+ {"navigationContentDescription", 21},
+ {"fragmentExitTransition", 21},
+ {"fragmentEnterTransition", 21},
+ {"fragmentSharedElementEnterTransition", 21},
+ {"fragmentReturnTransition", 21},
+ {"fragmentSharedElementReturnTransition", 21},
+ {"fragmentReenterTransition", 21},
+ {"fragmentAllowEnterTransitionOverlap", 21},
+ {"fragmentAllowReturnTransitionOverlap", 21},
+ {"patternPathData", 21},
+ {"strokeAlpha", 21},
+ {"fillAlpha", 21},
+ {"windowActivityTransitions", 21},
+ {"colorEdgeEffect", 21}};
size_t findAttributeSdkLevel(const ResourceName& name) {
- if (name.package != "android" && name.type != ResourceType::kAttr) {
- return 0;
- }
+ if (name.package != "android" && name.type != ResourceType::kAttr) {
+ return 0;
+ }
- auto iter = sAttrMap.find(name.entry);
- if (iter != sAttrMap.end()) {
- return iter->second;
- }
- return SDK_LOLLIPOP_MR1;
+ auto iter = sAttrMap.find(name.entry);
+ if (iter != sAttrMap.end()) {
+ return iter->second;
+ }
+ return SDK_LOLLIPOP_MR1;
}
std::pair<StringPiece, int> getDevelopmentSdkCodeNameAndVersion() {
- return std::make_pair(StringPiece(sDevelopmentSdkCodeName), sDevelopmentSdkLevel);
+ return std::make_pair(StringPiece(sDevelopmentSdkCodeName),
+ sDevelopmentSdkLevel);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index c9dbdca..bd17fe4 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -24,33 +24,33 @@
namespace aapt {
enum {
- SDK_CUPCAKE = 3,
- SDK_DONUT = 4,
- SDK_ECLAIR = 5,
- SDK_ECLAIR_0_1 = 6,
- SDK_ECLAIR_MR1 = 7,
- SDK_FROYO = 8,
- SDK_GINGERBREAD = 9,
- SDK_GINGERBREAD_MR1 = 10,
- SDK_HONEYCOMB = 11,
- SDK_HONEYCOMB_MR1 = 12,
- SDK_HONEYCOMB_MR2 = 13,
- SDK_ICE_CREAM_SANDWICH = 14,
- SDK_ICE_CREAM_SANDWICH_MR1 = 15,
- SDK_JELLY_BEAN = 16,
- SDK_JELLY_BEAN_MR1 = 17,
- SDK_JELLY_BEAN_MR2 = 18,
- SDK_KITKAT = 19,
- SDK_KITKAT_WATCH = 20,
- SDK_LOLLIPOP = 21,
- SDK_LOLLIPOP_MR1 = 22,
- SDK_MARSHMALLOW = 23,
+ SDK_CUPCAKE = 3,
+ SDK_DONUT = 4,
+ SDK_ECLAIR = 5,
+ SDK_ECLAIR_0_1 = 6,
+ SDK_ECLAIR_MR1 = 7,
+ SDK_FROYO = 8,
+ SDK_GINGERBREAD = 9,
+ SDK_GINGERBREAD_MR1 = 10,
+ SDK_HONEYCOMB = 11,
+ SDK_HONEYCOMB_MR1 = 12,
+ SDK_HONEYCOMB_MR2 = 13,
+ SDK_ICE_CREAM_SANDWICH = 14,
+ SDK_ICE_CREAM_SANDWICH_MR1 = 15,
+ SDK_JELLY_BEAN = 16,
+ SDK_JELLY_BEAN_MR1 = 17,
+ SDK_JELLY_BEAN_MR2 = 18,
+ SDK_KITKAT = 19,
+ SDK_KITKAT_WATCH = 20,
+ SDK_LOLLIPOP = 21,
+ SDK_LOLLIPOP_MR1 = 22,
+ SDK_MARSHMALLOW = 23,
};
size_t findAttributeSdkLevel(const ResourceId& id);
size_t findAttributeSdkLevel(const ResourceName& name);
std::pair<StringPiece, int> getDevelopmentSdkCodeNameAndVersion();
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_SDK_CONSTANTS_H
+#endif // AAPT_SDK_CONSTANTS_H
diff --git a/tools/aapt2/SdkConstants_test.cpp b/tools/aapt2/SdkConstants_test.cpp
index e81f412..3b70acb 100644
--- a/tools/aapt2/SdkConstants_test.cpp
+++ b/tools/aapt2/SdkConstants_test.cpp
@@ -21,18 +21,18 @@
namespace aapt {
TEST(SdkConstantsTest, FirstAttributeIsSdk1) {
- EXPECT_EQ(1u, findAttributeSdkLevel(ResourceId(0x01010000)));
+ EXPECT_EQ(1u, findAttributeSdkLevel(ResourceId(0x01010000)));
}
TEST(SdkConstantsTest, AllAttributesAfterLollipopAreLollipopMR1) {
- EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010103f7)));
- EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010104ce)));
+ EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010103f7)));
+ EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010104ce)));
- EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104cf)));
- EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d8)));
+ EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104cf)));
+ EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d8)));
- EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d9)));
- EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x0101ffff)));
+ EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d9)));
+ EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x0101ffff)));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
index 8a1021d..422b361 100644
--- a/tools/aapt2/Source.h
+++ b/tools/aapt2/Source.h
@@ -30,20 +30,19 @@
* showing errors.
*/
struct Source {
- std::string path;
- Maybe<size_t> line;
+ std::string path;
+ Maybe<size_t> line;
- Source() = default;
+ Source() = default;
- inline Source(const StringPiece& path) : path(path.toString()) { // NOLINT(implicit)
- }
+ inline Source(const StringPiece& path)
+ : path(path.toString()) { // NOLINT(implicit)
+ }
- inline Source(const StringPiece& path, size_t line) : path(path.toString()), line(line) {
- }
+ inline Source(const StringPiece& path, size_t line)
+ : path(path.toString()), line(line) {}
- inline Source withLine(size_t line) const {
- return Source(path, line);
- }
+ inline Source withLine(size_t line) const { return Source(path, line); }
};
//
@@ -51,30 +50,30 @@
//
inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) {
- out << source.path;
- if (source.line) {
- out << ":" << source.line.value();
- }
- return out;
+ out << source.path;
+ if (source.line) {
+ out << ":" << source.line.value();
+ }
+ return out;
}
inline bool operator==(const Source& lhs, const Source& rhs) {
- return lhs.path == rhs.path && lhs.line == rhs.line;
+ return lhs.path == rhs.path && lhs.line == rhs.line;
}
inline bool operator<(const Source& lhs, const Source& rhs) {
- int cmp = lhs.path.compare(rhs.path);
- if (cmp < 0) return true;
- if (cmp > 0) return false;
- if (lhs.line) {
- if (rhs.line) {
- return lhs.line.value() < rhs.line.value();
- }
- return false;
+ int cmp = lhs.path.compare(rhs.path);
+ if (cmp < 0) return true;
+ if (cmp > 0) return false;
+ if (lhs.line) {
+ if (rhs.line) {
+ return lhs.line.value() < rhs.line.value();
}
- return bool(rhs.line);
+ return false;
+ }
+ return bool(rhs.line);
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_SOURCE_H
+#endif // AAPT_SOURCE_H
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index fe4b967..a167a6a 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -19,395 +19,400 @@
#include "util/StringPiece.h"
#include "util/Util.h"
-#include <algorithm>
#include <androidfw/ResourceTypes.h>
+#include <algorithm>
#include <memory>
#include <string>
namespace aapt {
-StringPool::Ref::Ref() : mEntry(nullptr) {
-}
+StringPool::Ref::Ref() : mEntry(nullptr) {}
StringPool::Ref::Ref(const StringPool::Ref& rhs) : mEntry(rhs.mEntry) {
- if (mEntry != nullptr) {
- mEntry->ref++;
- }
+ if (mEntry != nullptr) {
+ mEntry->ref++;
+ }
}
StringPool::Ref::Ref(StringPool::Entry* entry) : mEntry(entry) {
- if (mEntry != nullptr) {
- mEntry->ref++;
- }
+ if (mEntry != nullptr) {
+ mEntry->ref++;
+ }
}
StringPool::Ref::~Ref() {
- if (mEntry != nullptr) {
- mEntry->ref--;
- }
+ if (mEntry != nullptr) {
+ mEntry->ref--;
+ }
}
StringPool::Ref& StringPool::Ref::operator=(const StringPool::Ref& rhs) {
- if (rhs.mEntry != nullptr) {
- rhs.mEntry->ref++;
- }
+ if (rhs.mEntry != nullptr) {
+ rhs.mEntry->ref++;
+ }
- if (mEntry != nullptr) {
- mEntry->ref--;
- }
- mEntry = rhs.mEntry;
- return *this;
+ if (mEntry != nullptr) {
+ mEntry->ref--;
+ }
+ mEntry = rhs.mEntry;
+ return *this;
}
const std::string* StringPool::Ref::operator->() const {
- return &mEntry->value;
+ return &mEntry->value;
}
-const std::string& StringPool::Ref::operator*() const {
- return mEntry->value;
-}
+const std::string& StringPool::Ref::operator*() const { return mEntry->value; }
-size_t StringPool::Ref::getIndex() const {
- return mEntry->index;
-}
+size_t StringPool::Ref::getIndex() const { return mEntry->index; }
const StringPool::Context& StringPool::Ref::getContext() const {
- return mEntry->context;
+ return mEntry->context;
}
-StringPool::StyleRef::StyleRef() : mEntry(nullptr) {
-}
+StringPool::StyleRef::StyleRef() : mEntry(nullptr) {}
-StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs) : mEntry(rhs.mEntry) {
- if (mEntry != nullptr) {
- mEntry->ref++;
- }
+StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs)
+ : mEntry(rhs.mEntry) {
+ if (mEntry != nullptr) {
+ mEntry->ref++;
+ }
}
StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : mEntry(entry) {
- if (mEntry != nullptr) {
- mEntry->ref++;
- }
+ if (mEntry != nullptr) {
+ mEntry->ref++;
+ }
}
StringPool::StyleRef::~StyleRef() {
- if (mEntry != nullptr) {
- mEntry->ref--;
- }
+ if (mEntry != nullptr) {
+ mEntry->ref--;
+ }
}
-StringPool::StyleRef& StringPool::StyleRef::operator=(const StringPool::StyleRef& rhs) {
- if (rhs.mEntry != nullptr) {
- rhs.mEntry->ref++;
- }
+StringPool::StyleRef& StringPool::StyleRef::operator=(
+ const StringPool::StyleRef& rhs) {
+ if (rhs.mEntry != nullptr) {
+ rhs.mEntry->ref++;
+ }
- if (mEntry != nullptr) {
- mEntry->ref--;
- }
- mEntry = rhs.mEntry;
- return *this;
+ if (mEntry != nullptr) {
+ mEntry->ref--;
+ }
+ mEntry = rhs.mEntry;
+ return *this;
}
const StringPool::StyleEntry* StringPool::StyleRef::operator->() const {
- return mEntry;
+ return mEntry;
}
const StringPool::StyleEntry& StringPool::StyleRef::operator*() const {
- return *mEntry;
+ return *mEntry;
}
-size_t StringPool::StyleRef::getIndex() const {
- return mEntry->str.getIndex();
-}
+size_t StringPool::StyleRef::getIndex() const { return mEntry->str.getIndex(); }
const StringPool::Context& StringPool::StyleRef::getContext() const {
- return mEntry->str.getContext();
+ return mEntry->str.getContext();
}
StringPool::Ref StringPool::makeRef(const StringPiece& str) {
- return makeRefImpl(str, Context{}, true);
+ return makeRefImpl(str, Context{}, true);
}
-StringPool::Ref StringPool::makeRef(const StringPiece& str, const Context& context) {
- return makeRefImpl(str, context, true);
+StringPool::Ref StringPool::makeRef(const StringPiece& str,
+ const Context& context) {
+ return makeRefImpl(str, context, true);
}
-StringPool::Ref StringPool::makeRefImpl(const StringPiece& str, const Context& context,
- bool unique) {
- if (unique) {
- auto iter = mIndexedStrings.find(str);
- if (iter != std::end(mIndexedStrings)) {
- return Ref(iter->second);
- }
+StringPool::Ref StringPool::makeRefImpl(const StringPiece& str,
+ const Context& context, bool unique) {
+ if (unique) {
+ auto iter = mIndexedStrings.find(str);
+ if (iter != std::end(mIndexedStrings)) {
+ return Ref(iter->second);
}
+ }
- Entry* entry = new Entry();
- entry->value = str.toString();
- entry->context = context;
- entry->index = mStrings.size();
- entry->ref = 0;
- mStrings.emplace_back(entry);
- mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry));
- return Ref(entry);
+ Entry* entry = new Entry();
+ entry->value = str.toString();
+ entry->context = context;
+ entry->index = mStrings.size();
+ entry->ref = 0;
+ mStrings.emplace_back(entry);
+ mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry));
+ return Ref(entry);
}
StringPool::StyleRef StringPool::makeRef(const StyleString& str) {
- return makeRef(str, Context{});
+ return makeRef(str, Context{});
}
-StringPool::StyleRef StringPool::makeRef(const StyleString& str, const Context& context) {
- Entry* entry = new Entry();
- entry->value = str.str;
- entry->context = context;
- entry->index = mStrings.size();
- entry->ref = 0;
- mStrings.emplace_back(entry);
- mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry));
+StringPool::StyleRef StringPool::makeRef(const StyleString& str,
+ const Context& context) {
+ Entry* entry = new Entry();
+ entry->value = str.str;
+ entry->context = context;
+ entry->index = mStrings.size();
+ entry->ref = 0;
+ mStrings.emplace_back(entry);
+ mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry));
- StyleEntry* styleEntry = new StyleEntry();
- styleEntry->str = Ref(entry);
- for (const aapt::Span& span : str.spans) {
- styleEntry->spans.emplace_back(Span{ makeRef(span.name), span.firstChar, span.lastChar });
- }
- styleEntry->ref = 0;
- mStyles.emplace_back(styleEntry);
- return StyleRef(styleEntry);
+ StyleEntry* styleEntry = new StyleEntry();
+ styleEntry->str = Ref(entry);
+ for (const aapt::Span& span : str.spans) {
+ styleEntry->spans.emplace_back(
+ Span{makeRef(span.name), span.firstChar, span.lastChar});
+ }
+ styleEntry->ref = 0;
+ mStyles.emplace_back(styleEntry);
+ return StyleRef(styleEntry);
}
StringPool::StyleRef StringPool::makeRef(const StyleRef& ref) {
- Entry* entry = new Entry();
- entry->value = *ref.mEntry->str;
- entry->context = ref.mEntry->str.mEntry->context;
- entry->index = mStrings.size();
- entry->ref = 0;
- mStrings.emplace_back(entry);
- mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry));
+ Entry* entry = new Entry();
+ entry->value = *ref.mEntry->str;
+ entry->context = ref.mEntry->str.mEntry->context;
+ entry->index = mStrings.size();
+ entry->ref = 0;
+ mStrings.emplace_back(entry);
+ mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry));
- StyleEntry* styleEntry = new StyleEntry();
- styleEntry->str = Ref(entry);
- for (const Span& span : ref.mEntry->spans) {
- styleEntry->spans.emplace_back(Span{ makeRef(*span.name), span.firstChar, span.lastChar });
- }
- styleEntry->ref = 0;
- mStyles.emplace_back(styleEntry);
- return StyleRef(styleEntry);
+ StyleEntry* styleEntry = new StyleEntry();
+ styleEntry->str = Ref(entry);
+ for (const Span& span : ref.mEntry->spans) {
+ styleEntry->spans.emplace_back(
+ Span{makeRef(*span.name), span.firstChar, span.lastChar});
+ }
+ styleEntry->ref = 0;
+ mStyles.emplace_back(styleEntry);
+ return StyleRef(styleEntry);
}
void StringPool::merge(StringPool&& pool) {
- mIndexedStrings.insert(pool.mIndexedStrings.begin(), pool.mIndexedStrings.end());
- pool.mIndexedStrings.clear();
- std::move(pool.mStrings.begin(), pool.mStrings.end(), std::back_inserter(mStrings));
- pool.mStrings.clear();
- std::move(pool.mStyles.begin(), pool.mStyles.end(), std::back_inserter(mStyles));
- pool.mStyles.clear();
+ mIndexedStrings.insert(pool.mIndexedStrings.begin(),
+ pool.mIndexedStrings.end());
+ pool.mIndexedStrings.clear();
+ std::move(pool.mStrings.begin(), pool.mStrings.end(),
+ std::back_inserter(mStrings));
+ pool.mStrings.clear();
+ std::move(pool.mStyles.begin(), pool.mStyles.end(),
+ std::back_inserter(mStyles));
+ pool.mStyles.clear();
- // Assign the indices.
- const size_t len = mStrings.size();
- for (size_t index = 0; index < len; index++) {
- mStrings[index]->index = index;
- }
+ // Assign the indices.
+ const size_t len = mStrings.size();
+ for (size_t index = 0; index < len; index++) {
+ mStrings[index]->index = index;
+ }
}
void StringPool::hintWillAdd(size_t stringCount, size_t styleCount) {
- mStrings.reserve(mStrings.size() + stringCount);
- mStyles.reserve(mStyles.size() + styleCount);
+ mStrings.reserve(mStrings.size() + stringCount);
+ mStyles.reserve(mStyles.size() + styleCount);
}
void StringPool::prune() {
- const auto iterEnd = std::end(mIndexedStrings);
- auto indexIter = std::begin(mIndexedStrings);
- while (indexIter != iterEnd) {
- if (indexIter->second->ref <= 0) {
- indexIter = mIndexedStrings.erase(indexIter);
- } else {
- ++indexIter;
- }
+ const auto iterEnd = std::end(mIndexedStrings);
+ auto indexIter = std::begin(mIndexedStrings);
+ while (indexIter != iterEnd) {
+ if (indexIter->second->ref <= 0) {
+ indexIter = mIndexedStrings.erase(indexIter);
+ } else {
+ ++indexIter;
}
+ }
- auto endIter2 = std::remove_if(std::begin(mStrings), std::end(mStrings),
- [](const std::unique_ptr<Entry>& entry) -> bool {
- return entry->ref <= 0;
- }
- );
+ auto endIter2 =
+ std::remove_if(std::begin(mStrings), std::end(mStrings),
+ [](const std::unique_ptr<Entry>& entry) -> bool {
+ return entry->ref <= 0;
+ });
- auto endIter3 = std::remove_if(std::begin(mStyles), std::end(mStyles),
- [](const std::unique_ptr<StyleEntry>& entry) -> bool {
- return entry->ref <= 0;
- }
- );
+ auto endIter3 =
+ std::remove_if(std::begin(mStyles), std::end(mStyles),
+ [](const std::unique_ptr<StyleEntry>& entry) -> bool {
+ return entry->ref <= 0;
+ });
- // Remove the entries at the end or else we'll be accessing
- // a deleted string from the StyleEntry.
- mStrings.erase(endIter2, std::end(mStrings));
- mStyles.erase(endIter3, std::end(mStyles));
+ // Remove the entries at the end or else we'll be accessing
+ // a deleted string from the StyleEntry.
+ mStrings.erase(endIter2, std::end(mStrings));
+ mStyles.erase(endIter3, std::end(mStyles));
- // Reassign the indices.
- const size_t len = mStrings.size();
- for (size_t index = 0; index < len; index++) {
- mStrings[index]->index = index;
- }
+ // Reassign the indices.
+ const size_t len = mStrings.size();
+ for (size_t index = 0; index < len; index++) {
+ mStrings[index]->index = index;
+ }
}
-void StringPool::sort(const std::function<bool(const Entry&, const Entry&)>& cmp) {
- std::sort(std::begin(mStrings), std::end(mStrings),
- [&cmp](const std::unique_ptr<Entry>& a, const std::unique_ptr<Entry>& b) -> bool {
- return cmp(*a, *b);
- }
- );
+void StringPool::sort(
+ const std::function<bool(const Entry&, const Entry&)>& cmp) {
+ std::sort(
+ std::begin(mStrings), std::end(mStrings),
+ [&cmp](const std::unique_ptr<Entry>& a,
+ const std::unique_ptr<Entry>& b) -> bool { return cmp(*a, *b); });
- // Assign the indices.
- const size_t len = mStrings.size();
- for (size_t index = 0; index < len; index++) {
- mStrings[index]->index = index;
- }
+ // Assign the indices.
+ const size_t len = mStrings.size();
+ for (size_t index = 0; index < len; index++) {
+ mStrings[index]->index = index;
+ }
- // Reorder the styles.
- std::sort(std::begin(mStyles), std::end(mStyles),
+ // Reorder the styles.
+ std::sort(std::begin(mStyles), std::end(mStyles),
[](const std::unique_ptr<StyleEntry>& lhs,
const std::unique_ptr<StyleEntry>& rhs) -> bool {
- return lhs->str.getIndex() < rhs->str.getIndex();
- }
- );
+ return lhs->str.getIndex() < rhs->str.getIndex();
+ });
}
template <typename T>
static T* encodeLength(T* data, size_t length) {
- static_assert(std::is_integral<T>::value, "wat.");
+ static_assert(std::is_integral<T>::value, "wat.");
- constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
- constexpr size_t kMaxSize = kMask - 1;
- if (length > kMaxSize) {
- *data++ = kMask | (kMaxSize & (length >> (sizeof(T) * 8)));
- }
- *data++ = length;
- return data;
+ constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
+ constexpr size_t kMaxSize = kMask - 1;
+ if (length > kMaxSize) {
+ *data++ = kMask | (kMaxSize & (length >> (sizeof(T) * 8)));
+ }
+ *data++ = length;
+ return data;
}
template <typename T>
static size_t encodedLengthUnits(size_t length) {
- static_assert(std::is_integral<T>::value, "wat.");
+ static_assert(std::is_integral<T>::value, "wat.");
- constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
- constexpr size_t kMaxSize = kMask - 1;
- return length > kMaxSize ? 2 : 1;
+ constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
+ constexpr size_t kMaxSize = kMask - 1;
+ return length > kMaxSize ? 2 : 1;
}
-
bool StringPool::flatten(BigBuffer* out, const StringPool& pool, bool utf8) {
- const size_t startIndex = out->size();
- android::ResStringPool_header* header = out->nextBlock<android::ResStringPool_header>();
- header->header.type = android::RES_STRING_POOL_TYPE;
- header->header.headerSize = sizeof(*header);
- header->stringCount = pool.size();
+ const size_t startIndex = out->size();
+ android::ResStringPool_header* header =
+ out->nextBlock<android::ResStringPool_header>();
+ header->header.type = android::RES_STRING_POOL_TYPE;
+ header->header.headerSize = sizeof(*header);
+ header->stringCount = pool.size();
+ if (utf8) {
+ header->flags |= android::ResStringPool_header::UTF8_FLAG;
+ }
+
+ uint32_t* indices =
+ pool.size() != 0 ? out->nextBlock<uint32_t>(pool.size()) : nullptr;
+
+ uint32_t* styleIndices = nullptr;
+ if (!pool.mStyles.empty()) {
+ header->styleCount = pool.mStyles.back()->str.getIndex() + 1;
+ styleIndices = out->nextBlock<uint32_t>(header->styleCount);
+ }
+
+ const size_t beforeStringsIndex = out->size();
+ header->stringsStart = beforeStringsIndex - startIndex;
+
+ for (const auto& entry : pool) {
+ *indices = out->size() - beforeStringsIndex;
+ indices++;
+
if (utf8) {
- header->flags |= android::ResStringPool_header::UTF8_FLAG;
+ const std::string& encoded = entry->value;
+ const ssize_t utf16Length = utf8_to_utf16_length(
+ reinterpret_cast<const uint8_t*>(entry->value.data()),
+ entry->value.size());
+ assert(utf16Length >= 0);
+
+ const size_t totalSize = encodedLengthUnits<char>(utf16Length) +
+ encodedLengthUnits<char>(encoded.length()) +
+ encoded.size() + 1;
+
+ char* data = out->nextBlock<char>(totalSize);
+
+ // First encode the UTF16 string length.
+ data = encodeLength(data, utf16Length);
+
+ // Now encode the size of the real UTF8 string.
+ data = encodeLength(data, encoded.length());
+ strncpy(data, encoded.data(), encoded.size());
+
+ } else {
+ const std::u16string encoded = util::utf8ToUtf16(entry->value);
+ const ssize_t utf16Length = encoded.size();
+
+ // Total number of 16-bit words to write.
+ const size_t totalSize =
+ encodedLengthUnits<char16_t>(utf16Length) + encoded.size() + 1;
+
+ char16_t* data = out->nextBlock<char16_t>(totalSize);
+
+ // Encode the actual UTF16 string length.
+ data = encodeLength(data, utf16Length);
+ const size_t byteLength = encoded.size() * sizeof(char16_t);
+
+ // NOTE: For some reason, strncpy16(data, entry->value.data(),
+ // entry->value.size())
+ // truncates the string.
+ memcpy(data, encoded.data(), byteLength);
+
+ // The null-terminating character is already here due to the block of data
+ // being set
+ // to 0s on allocation.
+ }
+ }
+
+ out->align4();
+
+ if (!pool.mStyles.empty()) {
+ const size_t beforeStylesIndex = out->size();
+ header->stylesStart = beforeStylesIndex - startIndex;
+
+ size_t currentIndex = 0;
+ for (const auto& entry : pool.mStyles) {
+ while (entry->str.getIndex() > currentIndex) {
+ styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
+
+ uint32_t* spanOffset = out->nextBlock<uint32_t>();
+ *spanOffset = android::ResStringPool_span::END;
+ }
+ styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
+
+ android::ResStringPool_span* span =
+ out->nextBlock<android::ResStringPool_span>(entry->spans.size());
+ for (const auto& s : entry->spans) {
+ span->name.index = s.name.getIndex();
+ span->firstChar = s.firstChar;
+ span->lastChar = s.lastChar;
+ span++;
+ }
+
+ uint32_t* spanEnd = out->nextBlock<uint32_t>();
+ *spanEnd = android::ResStringPool_span::END;
}
- uint32_t* indices = pool.size() != 0 ? out->nextBlock<uint32_t>(pool.size()) : nullptr;
-
- uint32_t* styleIndices = nullptr;
- if (!pool.mStyles.empty()) {
- header->styleCount = pool.mStyles.back()->str.getIndex() + 1;
- styleIndices = out->nextBlock<uint32_t>(header->styleCount);
- }
-
- const size_t beforeStringsIndex = out->size();
- header->stringsStart = beforeStringsIndex - startIndex;
-
- for (const auto& entry : pool) {
- *indices = out->size() - beforeStringsIndex;
- indices++;
-
- if (utf8) {
- const std::string& encoded = entry->value;
- const ssize_t utf16Length = utf8_to_utf16_length(
- reinterpret_cast<const uint8_t*>(entry->value.data()), entry->value.size());
- assert(utf16Length >= 0);
-
- const size_t totalSize = encodedLengthUnits<char>(utf16Length)
- + encodedLengthUnits<char>(encoded.length())
- + encoded.size() + 1;
-
- char* data = out->nextBlock<char>(totalSize);
-
- // First encode the UTF16 string length.
- data = encodeLength(data, utf16Length);
-
- // Now encode the size of the real UTF8 string.
- data = encodeLength(data, encoded.length());
- strncpy(data, encoded.data(), encoded.size());
-
- } else {
- const std::u16string encoded = util::utf8ToUtf16(entry->value);
- const ssize_t utf16Length = encoded.size();
-
- // Total number of 16-bit words to write.
- const size_t totalSize = encodedLengthUnits<char16_t>(utf16Length) + encoded.size() + 1;
-
- char16_t* data = out->nextBlock<char16_t>(totalSize);
-
- // Encode the actual UTF16 string length.
- data = encodeLength(data, utf16Length);
- const size_t byteLength = encoded.size() * sizeof(char16_t);
-
- // NOTE: For some reason, strncpy16(data, entry->value.data(), entry->value.size())
- // truncates the string.
- memcpy(data, encoded.data(), byteLength);
-
- // The null-terminating character is already here due to the block of data being set
- // to 0s on allocation.
- }
- }
-
+ // The error checking code in the platform looks for an entire
+ // ResStringPool_span structure worth of 0xFFFFFFFF at the end
+ // of the style block, so fill in the remaining 2 32bit words
+ // with 0xFFFFFFFF.
+ const size_t paddingLength = sizeof(android::ResStringPool_span) -
+ sizeof(android::ResStringPool_span::name);
+ uint8_t* padding = out->nextBlock<uint8_t>(paddingLength);
+ memset(padding, 0xff, paddingLength);
out->align4();
-
- if (!pool.mStyles.empty()) {
- const size_t beforeStylesIndex = out->size();
- header->stylesStart = beforeStylesIndex - startIndex;
-
- size_t currentIndex = 0;
- for (const auto& entry : pool.mStyles) {
- while (entry->str.getIndex() > currentIndex) {
- styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
-
- uint32_t* spanOffset = out->nextBlock<uint32_t>();
- *spanOffset = android::ResStringPool_span::END;
- }
- styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
-
- android::ResStringPool_span* span =
- out->nextBlock<android::ResStringPool_span>(entry->spans.size());
- for (const auto& s : entry->spans) {
- span->name.index = s.name.getIndex();
- span->firstChar = s.firstChar;
- span->lastChar = s.lastChar;
- span++;
- }
-
- uint32_t* spanEnd = out->nextBlock<uint32_t>();
- *spanEnd = android::ResStringPool_span::END;
- }
-
- // The error checking code in the platform looks for an entire
- // ResStringPool_span structure worth of 0xFFFFFFFF at the end
- // of the style block, so fill in the remaining 2 32bit words
- // with 0xFFFFFFFF.
- const size_t paddingLength = sizeof(android::ResStringPool_span)
- - sizeof(android::ResStringPool_span::name);
- uint8_t* padding = out->nextBlock<uint8_t>(paddingLength);
- memset(padding, 0xff, paddingLength);
- out->align4();
- }
- header->header.size = out->size() - startIndex;
- return true;
+ }
+ header->header.size = out->size() - startIndex;
+ return true;
}
bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) {
- return flatten(out, pool, true);
+ return flatten(out, pool, true);
}
bool StringPool::flattenUtf16(BigBuffer* out, const StringPool& pool) {
- return flatten(out, pool, false);
+ return flatten(out, pool, false);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index 13545be..6e0d646 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -17,207 +17,218 @@
#ifndef AAPT_STRING_POOL_H
#define AAPT_STRING_POOL_H
-#include "util/BigBuffer.h"
#include "ConfigDescription.h"
+#include "util/BigBuffer.h"
#include "util/StringPiece.h"
#include <functional>
-#include <unordered_map>
#include <memory>
#include <string>
+#include <unordered_map>
#include <vector>
namespace aapt {
struct Span {
- std::string name;
- uint32_t firstChar;
- uint32_t lastChar;
+ std::string name;
+ uint32_t firstChar;
+ uint32_t lastChar;
};
struct StyleString {
- std::string str;
- std::vector<Span> spans;
+ std::string str;
+ std::vector<Span> spans;
};
class StringPool {
-public:
- struct Context {
- uint32_t priority;
- ConfigDescription config;
+ public:
+ class Context {
+ public:
+ enum : uint32_t {
+ kStylePriority = 0u,
+ kHighPriority = 1u,
+ kNormalPriority = 0x7fffffffu,
+ kLowPriority = 0xffffffffu,
};
+ uint32_t priority = kNormalPriority;
+ ConfigDescription config;
- class Entry;
+ Context() = default;
+ Context(uint32_t p, const ConfigDescription& c) : priority(p), config(c) {}
+ explicit Context(uint32_t p) : priority(p) {}
+ explicit Context(const ConfigDescription& c)
+ : priority(kNormalPriority), config(c) {}
+ };
- class Ref {
- public:
- Ref();
- Ref(const Ref&);
- ~Ref();
+ class Entry;
- Ref& operator=(const Ref& rhs);
- const std::string* operator->() const;
- const std::string& operator*() const;
+ class Ref {
+ public:
+ Ref();
+ Ref(const Ref&);
+ ~Ref();
- size_t getIndex() const;
- const Context& getContext() const;
+ Ref& operator=(const Ref& rhs);
+ const std::string* operator->() const;
+ const std::string& operator*() const;
- private:
- friend class StringPool;
+ size_t getIndex() const;
+ const Context& getContext() const;
- explicit Ref(Entry* entry);
+ private:
+ friend class StringPool;
- Entry* mEntry;
- };
+ explicit Ref(Entry* entry);
- class StyleEntry;
+ Entry* mEntry;
+ };
- class StyleRef {
- public:
- StyleRef();
- StyleRef(const StyleRef&);
- ~StyleRef();
+ class StyleEntry;
- StyleRef& operator=(const StyleRef& rhs);
- const StyleEntry* operator->() const;
- const StyleEntry& operator*() const;
+ class StyleRef {
+ public:
+ StyleRef();
+ StyleRef(const StyleRef&);
+ ~StyleRef();
- size_t getIndex() const;
- const Context& getContext() const;
+ StyleRef& operator=(const StyleRef& rhs);
+ const StyleEntry* operator->() const;
+ const StyleEntry& operator*() const;
- private:
- friend class StringPool;
+ size_t getIndex() const;
+ const Context& getContext() const;
- explicit StyleRef(StyleEntry* entry);
+ private:
+ friend class StringPool;
- StyleEntry* mEntry;
- };
+ explicit StyleRef(StyleEntry* entry);
- class Entry {
- public:
- std::string value;
- Context context;
- size_t index;
+ StyleEntry* mEntry;
+ };
- private:
- friend class StringPool;
- friend class Ref;
+ class Entry {
+ public:
+ std::string value;
+ Context context;
+ size_t index;
- int ref;
- };
+ private:
+ friend class StringPool;
+ friend class Ref;
- struct Span {
- Ref name;
- uint32_t firstChar;
- uint32_t lastChar;
- };
+ int ref;
+ };
- class StyleEntry {
- public:
- Ref str;
- std::vector<Span> spans;
+ struct Span {
+ Ref name;
+ uint32_t firstChar;
+ uint32_t lastChar;
+ };
- private:
- friend class StringPool;
- friend class StyleRef;
+ class StyleEntry {
+ public:
+ Ref str;
+ std::vector<Span> spans;
- int ref;
- };
+ private:
+ friend class StringPool;
+ friend class StyleRef;
- using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator;
+ int ref;
+ };
- static bool flattenUtf8(BigBuffer* out, const StringPool& pool);
- static bool flattenUtf16(BigBuffer* out, const StringPool& pool);
+ using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator;
- StringPool() = default;
- StringPool(const StringPool&) = delete;
+ static bool flattenUtf8(BigBuffer* out, const StringPool& pool);
+ static bool flattenUtf16(BigBuffer* out, const StringPool& pool);
- /**
- * Adds a string to the pool, unless it already exists. Returns
- * a reference to the string in the pool.
- */
- Ref makeRef(const StringPiece& str);
+ StringPool() = default;
+ StringPool(const StringPool&) = delete;
- /**
- * Adds a string to the pool, unless it already exists, with a context
- * object that can be used when sorting the string pool. Returns
- * a reference to the string in the pool.
- */
- Ref makeRef(const StringPiece& str, const Context& context);
+ /**
+ * Adds a string to the pool, unless it already exists. Returns
+ * a reference to the string in the pool.
+ */
+ Ref makeRef(const StringPiece& str);
- /**
- * Adds a style to the string pool and returns a reference to it.
- */
- StyleRef makeRef(const StyleString& str);
+ /**
+ * Adds a string to the pool, unless it already exists, with a context
+ * object that can be used when sorting the string pool. Returns
+ * a reference to the string in the pool.
+ */
+ Ref makeRef(const StringPiece& str, const Context& context);
- /**
- * Adds a style to the string pool with a context object that
- * can be used when sorting the string pool. Returns a reference
- * to the style in the string pool.
- */
- StyleRef makeRef(const StyleString& str, const Context& context);
+ /**
+ * Adds a style to the string pool and returns a reference to it.
+ */
+ StyleRef makeRef(const StyleString& str);
- /**
- * Adds a style from another string pool. Returns a reference to the
- * style in the string pool.
- */
- StyleRef makeRef(const StyleRef& ref);
+ /**
+ * Adds a style to the string pool with a context object that
+ * can be used when sorting the string pool. Returns a reference
+ * to the style in the string pool.
+ */
+ StyleRef makeRef(const StyleString& str, const Context& context);
- /**
- * Moves pool into this one without coalescing strings. When this
- * function returns, pool will be empty.
- */
- void merge(StringPool&& pool);
+ /**
+ * Adds a style from another string pool. Returns a reference to the
+ * style in the string pool.
+ */
+ StyleRef makeRef(const StyleRef& ref);
- /**
- * Retuns the number of strings in the table.
- */
- inline size_t size() const;
+ /**
+ * Moves pool into this one without coalescing strings. When this
+ * function returns, pool will be empty.
+ */
+ void merge(StringPool&& pool);
- /**
- * Reserves space for strings and styles as an optimization.
- */
- void hintWillAdd(size_t stringCount, size_t styleCount);
+ /**
+ * Retuns the number of strings in the table.
+ */
+ inline size_t size() const;
- /**
- * Sorts the strings according to some comparison function.
- */
- void sort(const std::function<bool(const Entry&, const Entry&)>& cmp);
+ /**
+ * Reserves space for strings and styles as an optimization.
+ */
+ void hintWillAdd(size_t stringCount, size_t styleCount);
- /**
- * Removes any strings that have no references.
- */
- void prune();
+ /**
+ * Sorts the strings according to some comparison function.
+ */
+ void sort(const std::function<bool(const Entry&, const Entry&)>& cmp);
-private:
- friend const_iterator begin(const StringPool& pool);
- friend const_iterator end(const StringPool& pool);
+ /**
+ * Removes any strings that have no references.
+ */
+ void prune();
- static bool flatten(BigBuffer* out, const StringPool& pool, bool utf8);
+ private:
+ friend const_iterator begin(const StringPool& pool);
+ friend const_iterator end(const StringPool& pool);
- Ref makeRefImpl(const StringPiece& str, const Context& context, bool unique);
+ static bool flatten(BigBuffer* out, const StringPool& pool, bool utf8);
- std::vector<std::unique_ptr<Entry>> mStrings;
- std::vector<std::unique_ptr<StyleEntry>> mStyles;
- std::unordered_multimap<StringPiece, Entry*> mIndexedStrings;
+ Ref makeRefImpl(const StringPiece& str, const Context& context, bool unique);
+
+ std::vector<std::unique_ptr<Entry>> mStrings;
+ std::vector<std::unique_ptr<StyleEntry>> mStyles;
+ std::unordered_multimap<StringPiece, Entry*> mIndexedStrings;
};
//
// Inline implementation
//
-inline size_t StringPool::size() const {
- return mStrings.size();
-}
+inline size_t StringPool::size() const { return mStrings.size(); }
inline StringPool::const_iterator begin(const StringPool& pool) {
- return pool.mStrings.begin();
+ return pool.mStrings.begin();
}
inline StringPool::const_iterator end(const StringPool& pool) {
- return pool.mStrings.end();
+ return pool.mStrings.end();
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_STRING_POOL_H
+#endif // AAPT_STRING_POOL_H
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
index 1367af7..2a7e1dd 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -23,246 +23,245 @@
namespace aapt {
TEST(StringPoolTest, InsertOneString) {
- StringPool pool;
+ StringPool pool;
- StringPool::Ref ref = pool.makeRef("wut");
- EXPECT_EQ(*ref, "wut");
+ StringPool::Ref ref = pool.makeRef("wut");
+ EXPECT_EQ(*ref, "wut");
}
TEST(StringPoolTest, InsertTwoUniqueStrings) {
- StringPool pool;
+ StringPool pool;
- StringPool::Ref ref = pool.makeRef("wut");
- StringPool::Ref ref2 = pool.makeRef("hey");
+ StringPool::Ref ref = pool.makeRef("wut");
+ StringPool::Ref ref2 = pool.makeRef("hey");
- EXPECT_EQ(*ref, "wut");
- EXPECT_EQ(*ref2, "hey");
+ EXPECT_EQ(*ref, "wut");
+ EXPECT_EQ(*ref2, "hey");
}
TEST(StringPoolTest, DoNotInsertNewDuplicateString) {
- StringPool pool;
+ StringPool pool;
- StringPool::Ref ref = pool.makeRef("wut");
- StringPool::Ref ref2 = pool.makeRef("wut");
+ StringPool::Ref ref = pool.makeRef("wut");
+ StringPool::Ref ref2 = pool.makeRef("wut");
- EXPECT_EQ(*ref, "wut");
- EXPECT_EQ(*ref2, "wut");
- EXPECT_EQ(1u, pool.size());
+ EXPECT_EQ(*ref, "wut");
+ EXPECT_EQ(*ref2, "wut");
+ EXPECT_EQ(1u, pool.size());
}
TEST(StringPoolTest, MaintainInsertionOrderIndex) {
- StringPool pool;
+ StringPool pool;
- StringPool::Ref ref = pool.makeRef("z");
- StringPool::Ref ref2 = pool.makeRef("a");
- StringPool::Ref ref3 = pool.makeRef("m");
+ StringPool::Ref ref = pool.makeRef("z");
+ StringPool::Ref ref2 = pool.makeRef("a");
+ StringPool::Ref ref3 = pool.makeRef("m");
- EXPECT_EQ(0u, ref.getIndex());
- EXPECT_EQ(1u, ref2.getIndex());
- EXPECT_EQ(2u, ref3.getIndex());
+ EXPECT_EQ(0u, ref.getIndex());
+ EXPECT_EQ(1u, ref2.getIndex());
+ EXPECT_EQ(2u, ref3.getIndex());
}
TEST(StringPoolTest, PruneStringsWithNoReferences) {
- StringPool pool;
+ StringPool pool;
- StringPool::Ref refA = pool.makeRef("foo");
- {
- StringPool::Ref ref = pool.makeRef("wut");
- EXPECT_EQ(*ref, "wut");
- EXPECT_EQ(2u, pool.size());
- }
- StringPool::Ref refB = pool.makeRef("bar");
-
- EXPECT_EQ(3u, pool.size());
- pool.prune();
+ StringPool::Ref refA = pool.makeRef("foo");
+ {
+ StringPool::Ref ref = pool.makeRef("wut");
+ EXPECT_EQ(*ref, "wut");
EXPECT_EQ(2u, pool.size());
- StringPool::const_iterator iter = begin(pool);
- EXPECT_EQ((*iter)->value, "foo");
- EXPECT_LT((*iter)->index, 2u);
- ++iter;
- EXPECT_EQ((*iter)->value, "bar");
- EXPECT_LT((*iter)->index, 2u);
+ }
+ StringPool::Ref refB = pool.makeRef("bar");
+
+ EXPECT_EQ(3u, pool.size());
+ pool.prune();
+ EXPECT_EQ(2u, pool.size());
+ StringPool::const_iterator iter = begin(pool);
+ EXPECT_EQ((*iter)->value, "foo");
+ EXPECT_LT((*iter)->index, 2u);
+ ++iter;
+ EXPECT_EQ((*iter)->value, "bar");
+ EXPECT_LT((*iter)->index, 2u);
}
TEST(StringPoolTest, SortAndMaintainIndexesInReferences) {
- StringPool pool;
+ StringPool pool;
- StringPool::Ref ref = pool.makeRef("z");
- StringPool::StyleRef ref2 = pool.makeRef(StyleString{ {"a"} });
- StringPool::Ref ref3 = pool.makeRef("m");
+ StringPool::Ref ref = pool.makeRef("z");
+ StringPool::StyleRef ref2 = pool.makeRef(StyleString{{"a"}});
+ StringPool::Ref ref3 = pool.makeRef("m");
- EXPECT_EQ(*ref, "z");
- EXPECT_EQ(0u, ref.getIndex());
+ EXPECT_EQ(*ref, "z");
+ EXPECT_EQ(0u, ref.getIndex());
- EXPECT_EQ(*(ref2->str), "a");
- EXPECT_EQ(1u, ref2.getIndex());
+ EXPECT_EQ(*(ref2->str), "a");
+ EXPECT_EQ(1u, ref2.getIndex());
- EXPECT_EQ(*ref3, "m");
- EXPECT_EQ(2u, ref3.getIndex());
+ EXPECT_EQ(*ref3, "m");
+ EXPECT_EQ(2u, ref3.getIndex());
- pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
- return a.value < b.value;
- });
+ pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+ return a.value < b.value;
+ });
+ EXPECT_EQ(*ref, "z");
+ EXPECT_EQ(2u, ref.getIndex());
- EXPECT_EQ(*ref, "z");
- EXPECT_EQ(2u, ref.getIndex());
+ EXPECT_EQ(*(ref2->str), "a");
+ EXPECT_EQ(0u, ref2.getIndex());
- EXPECT_EQ(*(ref2->str), "a");
- EXPECT_EQ(0u, ref2.getIndex());
-
- EXPECT_EQ(*ref3, "m");
- EXPECT_EQ(1u, ref3.getIndex());
+ EXPECT_EQ(*ref3, "m");
+ EXPECT_EQ(1u, ref3.getIndex());
}
TEST(StringPoolTest, SortAndStillDedupe) {
- StringPool pool;
+ StringPool pool;
- StringPool::Ref ref = pool.makeRef("z");
- StringPool::Ref ref2 = pool.makeRef("a");
- StringPool::Ref ref3 = pool.makeRef("m");
+ StringPool::Ref ref = pool.makeRef("z");
+ StringPool::Ref ref2 = pool.makeRef("a");
+ StringPool::Ref ref3 = pool.makeRef("m");
- pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
- return a.value < b.value;
- });
+ pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+ return a.value < b.value;
+ });
- StringPool::Ref ref4 = pool.makeRef("z");
- StringPool::Ref ref5 = pool.makeRef("a");
- StringPool::Ref ref6 = pool.makeRef("m");
+ StringPool::Ref ref4 = pool.makeRef("z");
+ StringPool::Ref ref5 = pool.makeRef("a");
+ StringPool::Ref ref6 = pool.makeRef("m");
- EXPECT_EQ(ref4.getIndex(), ref.getIndex());
- EXPECT_EQ(ref5.getIndex(), ref2.getIndex());
- EXPECT_EQ(ref6.getIndex(), ref3.getIndex());
+ EXPECT_EQ(ref4.getIndex(), ref.getIndex());
+ EXPECT_EQ(ref5.getIndex(), ref2.getIndex());
+ EXPECT_EQ(ref6.getIndex(), ref3.getIndex());
}
TEST(StringPoolTest, AddStyles) {
- StringPool pool;
+ StringPool pool;
- StyleString str {
- { "android" },
- {
- Span{ { "b" }, 2, 6 }
- }
- };
+ StyleString str{{"android"}, {Span{{"b"}, 2, 6}}};
- StringPool::StyleRef ref = pool.makeRef(str);
+ StringPool::StyleRef ref = pool.makeRef(str);
- EXPECT_EQ(0u, ref.getIndex());
- EXPECT_EQ(std::string("android"), *(ref->str));
- ASSERT_EQ(1u, ref->spans.size());
+ EXPECT_EQ(0u, ref.getIndex());
+ EXPECT_EQ(std::string("android"), *(ref->str));
+ ASSERT_EQ(1u, ref->spans.size());
- const StringPool::Span& span = ref->spans.front();
- EXPECT_EQ(*(span.name), "b");
- EXPECT_EQ(2u, span.firstChar);
- EXPECT_EQ(6u, span.lastChar);
+ const StringPool::Span& span = ref->spans.front();
+ EXPECT_EQ(*(span.name), "b");
+ EXPECT_EQ(2u, span.firstChar);
+ EXPECT_EQ(6u, span.lastChar);
}
TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) {
- StringPool pool;
+ StringPool pool;
- StringPool::Ref ref = pool.makeRef("android");
+ StringPool::Ref ref = pool.makeRef("android");
- StyleString str { { "android" } };
- StringPool::StyleRef styleRef = pool.makeRef(str);
+ StyleString str{{"android"}};
+ StringPool::StyleRef styleRef = pool.makeRef(str);
- EXPECT_NE(ref.getIndex(), styleRef.getIndex());
+ EXPECT_NE(ref.getIndex(), styleRef.getIndex());
}
TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) {
- using namespace android; // For NO_ERROR on Windows.
+ using namespace android; // For NO_ERROR on Windows.
- StringPool pool;
- BigBuffer buffer(1024);
- StringPool::flattenUtf8(&buffer, pool);
+ StringPool pool;
+ BigBuffer buffer(1024);
+ StringPool::flattenUtf8(&buffer, pool);
- std::unique_ptr<uint8_t[]> data = util::copy(buffer);
- ResStringPool test;
- ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
}
TEST(StringPoolTest, FlattenOddCharactersUtf16) {
- using namespace android; // For NO_ERROR on Windows.
+ using namespace android; // For NO_ERROR on Windows.
- StringPool pool;
- pool.makeRef("\u093f");
- BigBuffer buffer(1024);
- StringPool::flattenUtf16(&buffer, pool);
+ StringPool pool;
+ pool.makeRef("\u093f");
+ BigBuffer buffer(1024);
+ StringPool::flattenUtf16(&buffer, pool);
- std::unique_ptr<uint8_t[]> data = util::copy(buffer);
- ResStringPool test;
- ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
- size_t len = 0;
- const char16_t* str = test.stringAt(0, &len);
- EXPECT_EQ(1u, len);
- EXPECT_EQ(u'\u093f', *str);
- EXPECT_EQ(0u, str[1]);
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
+ size_t len = 0;
+ const char16_t* str = test.stringAt(0, &len);
+ EXPECT_EQ(1u, len);
+ EXPECT_EQ(u'\u093f', *str);
+ EXPECT_EQ(0u, str[1]);
}
-constexpr const char* sLongString = "バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限します。メール、SMSや、同期を使 用するその他のアプリは、起動しても更新されないことがあります。バッテリーセーバーは端末の充電中は自動的にOFFになります。";
+constexpr const char* sLongString =
+ "バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑"
+ "え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限"
+ "します。メール、SMSや、同期を使 "
+ "用するその他のアプリは、起動しても更新されないことがあります。バッテリーセ"
+ "ーバーは端末の充電中は自動的にOFFになります。";
TEST(StringPoolTest, Flatten) {
- using namespace android; // For NO_ERROR on Windows.
+ using namespace android; // For NO_ERROR on Windows.
- StringPool pool;
+ StringPool pool;
- StringPool::Ref ref1 = pool.makeRef("hello");
- StringPool::Ref ref2 = pool.makeRef("goodbye");
- StringPool::Ref ref3 = pool.makeRef(sLongString);
- StringPool::Ref ref4 = pool.makeRef("");
- StringPool::StyleRef ref5 = pool.makeRef(StyleString{
- { "style" },
- { Span{ { "b" }, 0, 1 }, Span{ { "i" }, 2, 3 } }
- });
+ StringPool::Ref ref1 = pool.makeRef("hello");
+ StringPool::Ref ref2 = pool.makeRef("goodbye");
+ StringPool::Ref ref3 = pool.makeRef(sLongString);
+ StringPool::Ref ref4 = pool.makeRef("");
+ StringPool::StyleRef ref5 = pool.makeRef(
+ StyleString{{"style"}, {Span{{"b"}, 0, 1}, Span{{"i"}, 2, 3}}});
- EXPECT_EQ(0u, ref1.getIndex());
- EXPECT_EQ(1u, ref2.getIndex());
- EXPECT_EQ(2u, ref3.getIndex());
- EXPECT_EQ(3u, ref4.getIndex());
- EXPECT_EQ(4u, ref5.getIndex());
+ EXPECT_EQ(0u, ref1.getIndex());
+ EXPECT_EQ(1u, ref2.getIndex());
+ EXPECT_EQ(2u, ref3.getIndex());
+ EXPECT_EQ(3u, ref4.getIndex());
+ EXPECT_EQ(4u, ref5.getIndex());
- BigBuffer buffers[2] = { BigBuffer(1024), BigBuffer(1024) };
- StringPool::flattenUtf8(&buffers[0], pool);
- StringPool::flattenUtf16(&buffers[1], pool);
+ BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)};
+ StringPool::flattenUtf8(&buffers[0], pool);
+ StringPool::flattenUtf16(&buffers[1], pool);
- // Test both UTF-8 and UTF-16 buffers.
- for (const BigBuffer& buffer : buffers) {
- std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ // Test both UTF-8 and UTF-16 buffers.
+ for (const BigBuffer& buffer : buffers) {
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
- ResStringPool test;
- ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
+ ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
- EXPECT_EQ(std::string("hello"), util::getString(test, 0));
- EXPECT_EQ(StringPiece16(u"hello"), util::getString16(test, 0));
+ EXPECT_EQ(std::string("hello"), util::getString(test, 0));
+ EXPECT_EQ(StringPiece16(u"hello"), util::getString16(test, 0));
- EXPECT_EQ(std::string("goodbye"), util::getString(test, 1));
- EXPECT_EQ(StringPiece16(u"goodbye"), util::getString16(test, 1));
+ EXPECT_EQ(std::string("goodbye"), util::getString(test, 1));
+ EXPECT_EQ(StringPiece16(u"goodbye"), util::getString16(test, 1));
- EXPECT_EQ(StringPiece(sLongString), util::getString(test, 2));
- EXPECT_EQ(util::utf8ToUtf16(sLongString), util::getString16(test, 2).toString());
+ EXPECT_EQ(StringPiece(sLongString), util::getString(test, 2));
+ EXPECT_EQ(util::utf8ToUtf16(sLongString),
+ util::getString16(test, 2).toString());
- size_t len;
- EXPECT_TRUE(test.stringAt(3, &len) != nullptr || test.string8At(3, &len) != nullptr);
+ size_t len;
+ EXPECT_TRUE(test.stringAt(3, &len) != nullptr ||
+ test.string8At(3, &len) != nullptr);
- EXPECT_EQ(std::string("style"), util::getString(test, 4));
- EXPECT_EQ(StringPiece16(u"style"), util::getString16(test, 4));
+ EXPECT_EQ(std::string("style"), util::getString(test, 4));
+ EXPECT_EQ(StringPiece16(u"style"), util::getString16(test, 4));
- const ResStringPool_span* span = test.styleAt(4);
- ASSERT_NE(nullptr, span);
- EXPECT_EQ(std::string("b"), util::getString(test, span->name.index));
- EXPECT_EQ(StringPiece16(u"b"), util::getString16(test, span->name.index));
- EXPECT_EQ(0u, span->firstChar);
- EXPECT_EQ(1u, span->lastChar);
- span++;
+ const ResStringPool_span* span = test.styleAt(4);
+ ASSERT_NE(nullptr, span);
+ EXPECT_EQ(std::string("b"), util::getString(test, span->name.index));
+ EXPECT_EQ(StringPiece16(u"b"), util::getString16(test, span->name.index));
+ EXPECT_EQ(0u, span->firstChar);
+ EXPECT_EQ(1u, span->lastChar);
+ span++;
- ASSERT_NE(ResStringPool_span::END, span->name.index);
- EXPECT_EQ(std::string("i"), util::getString(test, span->name.index));
- EXPECT_EQ(StringPiece16(u"i"), util::getString16(test, span->name.index));
- EXPECT_EQ(2u, span->firstChar);
- EXPECT_EQ(3u, span->lastChar);
- span++;
+ ASSERT_NE(ResStringPool_span::END, span->name.index);
+ EXPECT_EQ(std::string("i"), util::getString(test, span->name.index));
+ EXPECT_EQ(StringPiece16(u"i"), util::getString16(test, span->name.index));
+ EXPECT_EQ(2u, span->firstChar);
+ EXPECT_EQ(3u, span->lastChar);
+ span++;
- EXPECT_EQ(ResStringPool_span::END, span->name.index);
- }
+ EXPECT_EQ(ResStringPool_span::END, span->name.index);
+ }
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
index 9dc6a9c..121c337 100644
--- a/tools/aapt2/ValueVisitor.h
+++ b/tools/aapt2/ValueVisitor.h
@@ -17,91 +17,94 @@
#ifndef AAPT_VALUE_VISITOR_H
#define AAPT_VALUE_VISITOR_H
-#include "ResourceValues.h"
#include "ResourceTable.h"
+#include "ResourceValues.h"
namespace aapt {
/**
- * Visits a value and invokes the appropriate method based on its type. Does not traverse
+ * Visits a value and invokes the appropriate method based on its type. Does not
+ * traverse
* into compound types. Use ValueVisitor for that.
*/
struct RawValueVisitor {
- virtual ~RawValueVisitor() = default;
+ virtual ~RawValueVisitor() = default;
- virtual void visitItem(Item* value) {}
- virtual void visit(Reference* value) { visitItem(value); }
- virtual void visit(RawString* value) { visitItem(value); }
- virtual void visit(String* value) { visitItem(value); }
- virtual void visit(StyledString* value) { visitItem(value); }
- virtual void visit(FileReference* value) { visitItem(value); }
- virtual void visit(Id* value) { visitItem(value); }
- virtual void visit(BinaryPrimitive* value) { visitItem(value); }
+ virtual void visitItem(Item* value) {}
+ virtual void visit(Reference* value) { visitItem(value); }
+ virtual void visit(RawString* value) { visitItem(value); }
+ virtual void visit(String* value) { visitItem(value); }
+ virtual void visit(StyledString* value) { visitItem(value); }
+ virtual void visit(FileReference* value) { visitItem(value); }
+ virtual void visit(Id* value) { visitItem(value); }
+ virtual void visit(BinaryPrimitive* value) { visitItem(value); }
- virtual void visit(Attribute* value) {}
- virtual void visit(Style* value) {}
- virtual void visit(Array* value) {}
- virtual void visit(Plural* value) {}
- virtual void visit(Styleable* value) {}
+ virtual void visit(Attribute* value) {}
+ virtual void visit(Style* value) {}
+ virtual void visit(Array* value) {}
+ virtual void visit(Plural* value) {}
+ virtual void visit(Styleable* value) {}
};
// NOLINT, do not add parentheses around T.
-#define DECL_VISIT_COMPOUND_VALUE(T) \
- virtual void visit(T* value) { /* NOLINT */ \
- visitSubValues(value); \
- }
+#define DECL_VISIT_COMPOUND_VALUE(T) \
+ virtual void visit(T* value) { /* NOLINT */ \
+ visitSubValues(value); \
+ }
/**
- * Visits values, and if they are compound values, visits the components as well.
+ * Visits values, and if they are compound values, visits the components as
+ * well.
*/
struct ValueVisitor : public RawValueVisitor {
- // The compiler will think we're hiding an overload, when we actually intend
- // to call into RawValueVisitor. This will expose the visit methods in the super
- // class so the compiler knows we are trying to call them.
- using RawValueVisitor::visit;
+ // The compiler will think we're hiding an overload, when we actually intend
+ // to call into RawValueVisitor. This will expose the visit methods in the
+ // super
+ // class so the compiler knows we are trying to call them.
+ using RawValueVisitor::visit;
- void visitSubValues(Attribute* attribute) {
- for (Attribute::Symbol& symbol : attribute->symbols) {
- visit(&symbol.symbol);
- }
+ void visitSubValues(Attribute* attribute) {
+ for (Attribute::Symbol& symbol : attribute->symbols) {
+ visit(&symbol.symbol);
+ }
+ }
+
+ void visitSubValues(Style* style) {
+ if (style->parent) {
+ visit(&style->parent.value());
}
- void visitSubValues(Style* style) {
- if (style->parent) {
- visit(&style->parent.value());
- }
-
- for (Style::Entry& entry : style->entries) {
- visit(&entry.key);
- entry.value->accept(this);
- }
+ for (Style::Entry& entry : style->entries) {
+ visit(&entry.key);
+ entry.value->accept(this);
}
+ }
- void visitSubValues(Array* array) {
- for (std::unique_ptr<Item>& item : array->items) {
- item->accept(this);
- }
+ void visitSubValues(Array* array) {
+ for (std::unique_ptr<Item>& item : array->items) {
+ item->accept(this);
}
+ }
- void visitSubValues(Plural* plural) {
- for (std::unique_ptr<Item>& item : plural->values) {
- if (item) {
- item->accept(this);
- }
- }
+ void visitSubValues(Plural* plural) {
+ for (std::unique_ptr<Item>& item : plural->values) {
+ if (item) {
+ item->accept(this);
+ }
}
+ }
- void visitSubValues(Styleable* styleable) {
- for (Reference& reference : styleable->entries) {
- visit(&reference);
- }
+ void visitSubValues(Styleable* styleable) {
+ for (Reference& reference : styleable->entries) {
+ visit(&reference);
}
+ }
- DECL_VISIT_COMPOUND_VALUE(Attribute);
- DECL_VISIT_COMPOUND_VALUE(Style);
- DECL_VISIT_COMPOUND_VALUE(Array);
- DECL_VISIT_COMPOUND_VALUE(Plural);
- DECL_VISIT_COMPOUND_VALUE(Styleable);
+ DECL_VISIT_COMPOUND_VALUE(Attribute);
+ DECL_VISIT_COMPOUND_VALUE(Style);
+ DECL_VISIT_COMPOUND_VALUE(Array);
+ DECL_VISIT_COMPOUND_VALUE(Plural);
+ DECL_VISIT_COMPOUND_VALUE(Styleable);
};
/**
@@ -109,11 +112,9 @@
*/
template <typename T>
struct DynCastVisitor : public RawValueVisitor {
- T* value = nullptr;
+ T* value = nullptr;
- void visit(T* v) override {
- value = v;
- }
+ void visit(T* v) override { value = v; }
};
/**
@@ -121,16 +122,14 @@
*/
template <>
struct DynCastVisitor<Item> : public RawValueVisitor {
- Item* value = nullptr;
+ Item* value = nullptr;
- void visitItem(Item* item) override {
- value = item;
- }
+ void visitItem(Item* item) override { value = item; }
};
template <typename T>
const T* valueCast(const Value* value) {
- return valueCast<T>(const_cast<Value*>(value));
+ return valueCast<T>(const_cast<Value*>(value));
}
/**
@@ -139,30 +138,32 @@
*/
template <typename T>
T* valueCast(Value* value) {
- if (!value) {
- return nullptr;
- }
- DynCastVisitor<T> visitor;
- value->accept(&visitor);
- return visitor.value;
+ if (!value) {
+ return nullptr;
+ }
+ DynCastVisitor<T> visitor;
+ value->accept(&visitor);
+ return visitor.value;
}
-inline void visitAllValuesInPackage(ResourceTablePackage* pkg, RawValueVisitor* visitor) {
- for (auto& type : pkg->types) {
- for (auto& entry : type->entries) {
- for (auto& configValue : entry->values) {
- configValue->value->accept(visitor);
- }
- }
+inline void visitAllValuesInPackage(ResourceTablePackage* pkg,
+ RawValueVisitor* visitor) {
+ for (auto& type : pkg->types) {
+ for (auto& entry : type->entries) {
+ for (auto& configValue : entry->values) {
+ configValue->value->accept(visitor);
+ }
}
+ }
}
-inline void visitAllValuesInTable(ResourceTable* table, RawValueVisitor* visitor) {
- for (auto& pkg : table->packages) {
- visitAllValuesInPackage(pkg.get(), visitor);
- }
+inline void visitAllValuesInTable(ResourceTable* table,
+ RawValueVisitor* visitor) {
+ for (auto& pkg : table->packages) {
+ visitAllValuesInPackage(pkg.get(), visitor);
+ }
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_VALUE_VISITOR_H
+#endif // AAPT_VALUE_VISITOR_H
diff --git a/tools/aapt2/ValueVisitor_test.cpp b/tools/aapt2/ValueVisitor_test.cpp
index 54e9fcd..ed9c7f6 100644
--- a/tools/aapt2/ValueVisitor_test.cpp
+++ b/tools/aapt2/ValueVisitor_test.cpp
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#include "ResourceValues.h"
#include "ValueVisitor.h"
+#include "ResourceValues.h"
#include "test/Test.h"
#include "util/Util.h"
@@ -24,63 +24,63 @@
namespace aapt {
struct SingleReferenceVisitor : public ValueVisitor {
- using ValueVisitor::visit;
+ using ValueVisitor::visit;
- Reference* visited = nullptr;
+ Reference* visited = nullptr;
- void visit(Reference* ref) override {
- visited = ref;
- }
+ void visit(Reference* ref) override { visited = ref; }
};
struct StyleVisitor : public ValueVisitor {
- using ValueVisitor::visit;
+ using ValueVisitor::visit;
- std::list<Reference*> visitedRefs;
- Style* visitedStyle = nullptr;
+ std::list<Reference*> visitedRefs;
+ Style* visitedStyle = nullptr;
- void visit(Reference* ref) override {
- visitedRefs.push_back(ref);
- }
+ void visit(Reference* ref) override { visitedRefs.push_back(ref); }
- void visit(Style* style) override {
- visitedStyle = style;
- ValueVisitor::visit(style);
- }
+ void visit(Style* style) override {
+ visitedStyle = style;
+ ValueVisitor::visit(style);
+ }
};
TEST(ValueVisitorTest, VisitsReference) {
- Reference ref(ResourceName{"android", ResourceType::kAttr, "foo"});
- SingleReferenceVisitor visitor;
- ref.accept(&visitor);
+ Reference ref(ResourceName{"android", ResourceType::kAttr, "foo"});
+ SingleReferenceVisitor visitor;
+ ref.accept(&visitor);
- EXPECT_EQ(visitor.visited, &ref);
+ EXPECT_EQ(visitor.visited, &ref);
}
TEST(ValueVisitorTest, VisitsReferencesInStyle) {
- std::unique_ptr<Style> style = test::StyleBuilder()
- .setParent("android:style/foo")
- .addItem("android:attr/one", test::buildReference("android:id/foo"))
- .build();
+ std::unique_ptr<Style> style =
+ test::StyleBuilder()
+ .setParent("android:style/foo")
+ .addItem("android:attr/one", test::buildReference("android:id/foo"))
+ .build();
- StyleVisitor visitor;
- style->accept(&visitor);
+ StyleVisitor visitor;
+ style->accept(&visitor);
- ASSERT_EQ(style.get(), visitor.visitedStyle);
+ ASSERT_EQ(style.get(), visitor.visitedStyle);
- // Entry attribute references, plus the parent reference, plus one value reference.
- ASSERT_EQ(style->entries.size() + 2, visitor.visitedRefs.size());
+ // Entry attribute references, plus the parent reference, plus one value
+ // reference.
+ ASSERT_EQ(style->entries.size() + 2, visitor.visitedRefs.size());
}
TEST(ValueVisitorTest, ValueCast) {
- std::unique_ptr<Reference> ref = test::buildReference("android:color/white");
- EXPECT_NE(valueCast<Reference>(ref.get()), nullptr);
+ std::unique_ptr<Reference> ref = test::buildReference("android:color/white");
+ EXPECT_NE(valueCast<Reference>(ref.get()), nullptr);
- std::unique_ptr<Style> style = test::StyleBuilder()
- .addItem("android:attr/foo", test::buildReference("android:color/black"))
- .build();
- EXPECT_NE(valueCast<Style>(style.get()), nullptr);
- EXPECT_EQ(valueCast<Reference>(style.get()), nullptr);
+ std::unique_ptr<Style> style =
+ test::StyleBuilder()
+ .addItem("android:attr/foo",
+ test::buildReference("android:color/black"))
+ .build();
+ EXPECT_NE(valueCast<Style>(style.get()), nullptr);
+ EXPECT_EQ(valueCast<Reference>(style.get()), nullptr);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index dbd8062..2ea63d0 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -33,8 +33,8 @@
#include "xml/XmlDom.h"
#include "xml/XmlPullParser.h"
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <android-base/errors.h>
#include <android-base/file.h>
@@ -48,16 +48,18 @@
namespace aapt {
struct ResourcePathData {
- Source source;
- std::string resourceDir;
- std::string name;
- std::string extension;
+ Source source;
+ std::string resourceDir;
+ std::string name;
+ std::string extension;
- // Original config str. We keep this because when we parse the config, we may add on
- // version qualifiers. We want to preserve the original input so the output is easily
- // computed before hand.
- std::string configStr;
- ConfigDescription config;
+ // Original config str. We keep this because when we parse the config, we may
+ // add on
+ // version qualifiers. We want to preserve the original input so the output is
+ // easily
+ // computed before hand.
+ std::string configStr;
+ ConfigDescription config;
};
/**
@@ -66,696 +68,735 @@
*/
static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
std::string* outError) {
- std::vector<std::string> parts = util::split(path, file::sDirSep);
- if (parts.size() < 2) {
- if (outError) *outError = "bad resource path";
- return {};
+ std::vector<std::string> parts = util::split(path, file::sDirSep);
+ if (parts.size() < 2) {
+ if (outError) *outError = "bad resource path";
+ return {};
+ }
+
+ std::string& dir = parts[parts.size() - 2];
+ StringPiece dirStr = dir;
+
+ StringPiece configStr;
+ ConfigDescription config;
+ size_t dashPos = dir.find('-');
+ if (dashPos != std::string::npos) {
+ configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
+ if (!ConfigDescription::parse(configStr, &config)) {
+ if (outError) {
+ std::stringstream errStr;
+ errStr << "invalid configuration '" << configStr << "'";
+ *outError = errStr.str();
+ }
+ return {};
}
+ dirStr = dirStr.substr(0, dashPos);
+ }
- std::string& dir = parts[parts.size() - 2];
- StringPiece dirStr = dir;
+ std::string& filename = parts[parts.size() - 1];
+ StringPiece name = filename;
+ StringPiece extension;
+ size_t dotPos = filename.find('.');
+ if (dotPos != std::string::npos) {
+ extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
+ name = name.substr(0, dotPos);
+ }
- StringPiece configStr;
- ConfigDescription config;
- size_t dashPos = dir.find('-');
- if (dashPos != std::string::npos) {
- configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
- if (!ConfigDescription::parse(configStr, &config)) {
- if (outError) {
- std::stringstream errStr;
- errStr << "invalid configuration '" << configStr << "'";
- *outError = errStr.str();
- }
- return {};
- }
- dirStr = dirStr.substr(0, dashPos);
- }
-
- std::string& filename = parts[parts.size() - 1];
- StringPiece name = filename;
- StringPiece extension;
- size_t dotPos = filename.find('.');
- if (dotPos != std::string::npos) {
- extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
- name = name.substr(0, dotPos);
- }
-
- return ResourcePathData{
- Source(path),
- dirStr.toString(),
- name.toString(),
- extension.toString(),
- configStr.toString(),
- config
- };
+ return ResourcePathData{Source(path), dirStr.toString(),
+ name.toString(), extension.toString(),
+ configStr.toString(), config};
}
struct CompileOptions {
- std::string outputPath;
- Maybe<std::string> resDir;
- bool pseudolocalize = false;
- bool legacyMode = false;
- bool verbose = false;
+ std::string outputPath;
+ Maybe<std::string> resDir;
+ bool pseudolocalize = false;
+ bool legacyMode = false;
+ bool verbose = false;
};
static std::string buildIntermediateFilename(const ResourcePathData& data) {
- std::stringstream name;
- name << data.resourceDir;
- if (!data.configStr.empty()) {
- name << "-" << data.configStr;
- }
- name << "_" << data.name;
- if (!data.extension.empty()) {
- name << "." << data.extension;
- }
- name << ".flat";
- return name.str();
+ std::stringstream name;
+ name << data.resourceDir;
+ if (!data.configStr.empty()) {
+ name << "-" << data.configStr;
+ }
+ name << "_" << data.name;
+ if (!data.extension.empty()) {
+ name << "." << data.extension;
+ }
+ name << ".flat";
+ return name.str();
}
static bool isHidden(const StringPiece& filename) {
- return util::stringStartsWith(filename, ".");
+ return util::stringStartsWith(filename, ".");
}
/**
* Walks the res directory structure, looking for resource files.
*/
-static bool loadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,
+static bool loadInputFilesFromDir(IAaptContext* context,
+ const CompileOptions& options,
std::vector<ResourcePathData>* outPathData) {
- const std::string& rootDir = options.resDir.value();
- std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()), closedir);
- if (!d) {
- context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+ const std::string& rootDir = options.resDir.value();
+ std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()),
+ closedir);
+ if (!d) {
+ context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ while (struct dirent* entry = readdir(d.get())) {
+ if (isHidden(entry->d_name)) {
+ continue;
+ }
+
+ std::string prefixPath = rootDir;
+ file::appendPath(&prefixPath, entry->d_name);
+
+ if (file::getFileType(prefixPath) != file::FileType::kDirectory) {
+ continue;
+ }
+
+ std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()),
+ closedir);
+ if (!subDir) {
+ context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ while (struct dirent* leafEntry = readdir(subDir.get())) {
+ if (isHidden(leafEntry->d_name)) {
+ continue;
+ }
+
+ std::string fullPath = prefixPath;
+ file::appendPath(&fullPath, leafEntry->d_name);
+
+ std::string errStr;
+ Maybe<ResourcePathData> pathData =
+ extractResourcePathData(fullPath, &errStr);
+ if (!pathData) {
+ context->getDiagnostics()->error(DiagMessage() << errStr);
return false;
+ }
+
+ outPathData->push_back(std::move(pathData.value()));
}
-
- while (struct dirent* entry = readdir(d.get())) {
- if (isHidden(entry->d_name)) {
- continue;
- }
-
- std::string prefixPath = rootDir;
- file::appendPath(&prefixPath, entry->d_name);
-
- if (file::getFileType(prefixPath) != file::FileType::kDirectory) {
- continue;
- }
-
- std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()), closedir);
- if (!subDir) {
- context->getDiagnostics()->error(DiagMessage() << strerror(errno));
- return false;
- }
-
- while (struct dirent* leafEntry = readdir(subDir.get())) {
- if (isHidden(leafEntry->d_name)) {
- continue;
- }
-
- std::string fullPath = prefixPath;
- file::appendPath(&fullPath, leafEntry->d_name);
-
- std::string errStr;
- Maybe<ResourcePathData> pathData = extractResourcePathData(fullPath, &errStr);
- if (!pathData) {
- context->getDiagnostics()->error(DiagMessage() << errStr);
- return false;
- }
-
- outPathData->push_back(std::move(pathData.value()));
- }
- }
- return true;
+ }
+ return true;
}
static bool compileTable(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, IArchiveWriter* writer,
+ const ResourcePathData& pathData,
+ IArchiveWriter* writer,
const std::string& outputPath) {
- ResourceTable table;
- {
- std::ifstream fin(pathData.source.path, std::ifstream::binary);
- if (!fin) {
- context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
- return false;
- }
-
-
- // Parse the values file from XML.
- xml::XmlPullParser xmlParser(fin);
-
- ResourceParserOptions parserOptions;
- parserOptions.errorOnPositionalArguments = !options.legacyMode;
-
- // If the filename includes donottranslate, then the default translatable is false.
- parserOptions.translatable = pathData.name.find("donottranslate") == std::string::npos;
-
- ResourceParser resParser(context->getDiagnostics(), &table, pathData.source,
- pathData.config, parserOptions);
- if (!resParser.parse(&xmlParser)) {
- return false;
- }
-
- fin.close();
+ ResourceTable table;
+ {
+ std::ifstream fin(pathData.source.path, std::ifstream::binary);
+ if (!fin) {
+ context->getDiagnostics()->error(DiagMessage(pathData.source)
+ << strerror(errno));
+ return false;
}
- if (options.pseudolocalize) {
- // Generate pseudo-localized strings (en-XA and ar-XB).
- // These are created as weak symbols, and are only generated from default configuration
- // strings and plurals.
- PseudolocaleGenerator pseudolocaleGenerator;
- if (!pseudolocaleGenerator.consume(context, &table)) {
- return false;
- }
+ // Parse the values file from XML.
+ xml::XmlPullParser xmlParser(fin);
+
+ ResourceParserOptions parserOptions;
+ parserOptions.errorOnPositionalArguments = !options.legacyMode;
+
+ // If the filename includes donottranslate, then the default translatable is
+ // false.
+ parserOptions.translatable =
+ pathData.name.find("donottranslate") == std::string::npos;
+
+ ResourceParser resParser(context->getDiagnostics(), &table, pathData.source,
+ pathData.config, parserOptions);
+ if (!resParser.parse(&xmlParser)) {
+ return false;
}
- // Ensure we have the compilation package at least.
- table.createPackage(context->getCompilationPackage());
+ fin.close();
+ }
- // Assign an ID to any package that has resources.
- for (auto& pkg : table.packages) {
- if (!pkg->id) {
- // If no package ID was set while parsing (public identifiers), auto assign an ID.
- pkg->id = context->getPackageId();
- }
+ if (options.pseudolocalize) {
+ // Generate pseudo-localized strings (en-XA and ar-XB).
+ // These are created as weak symbols, and are only generated from default
+ // configuration
+ // strings and plurals.
+ PseudolocaleGenerator pseudolocaleGenerator;
+ if (!pseudolocaleGenerator.consume(context, &table)) {
+ return false;
}
+ }
- // Create the file/zip entry.
- if (!writer->startEntry(outputPath, 0)) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
- return false;
+ // Ensure we have the compilation package at least.
+ table.createPackage(context->getCompilationPackage());
+
+ // Assign an ID to any package that has resources.
+ for (auto& pkg : table.packages) {
+ if (!pkg->id) {
+ // If no package ID was set while parsing (public identifiers), auto
+ // assign an ID.
+ pkg->id = context->getPackageId();
}
+ }
- // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
- {
- // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
- // interface.
- CopyingOutputStreamAdaptor copyingAdaptor(writer);
+ // Create the file/zip entry.
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath)
+ << "failed to open");
+ return false;
+ }
- std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table);
- if (!pbTable->SerializeToZeroCopyStream(©ingAdaptor)) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
- return false;
- }
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call
+ // writer->finishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the
+ // ZeroCopyOutputStream
+ // interface.
+ CopyingOutputStreamAdaptor copyingAdaptor(writer);
+
+ std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table);
+ if (!pbTable->SerializeToZeroCopyStream(©ingAdaptor)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath)
+ << "failed to write");
+ return false;
}
+ }
- if (!writer->finishEntry()) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to finish entry");
- return false;
- }
- return true;
+ if (!writer->finishEntry()) {
+ context->getDiagnostics()->error(DiagMessage(outputPath)
+ << "failed to finish entry");
+ return false;
+ }
+ return true;
}
-static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath, const ResourceFile& file,
- const BigBuffer& buffer, IArchiveWriter* writer,
+static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath,
+ const ResourceFile& file,
+ const BigBuffer& buffer,
+ IArchiveWriter* writer,
IDiagnostics* diag) {
- // Start the entry so we can write the header.
- if (!writer->startEntry(outputPath, 0)) {
- diag->error(DiagMessage(outputPath) << "failed to open file");
- return false;
+ // Start the entry so we can write the header.
+ if (!writer->startEntry(outputPath, 0)) {
+ diag->error(DiagMessage(outputPath) << "failed to open file");
+ return false;
+ }
+
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call
+ // writer->finishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the
+ // ZeroCopyOutputStream
+ // interface.
+ CopyingOutputStreamAdaptor copyingAdaptor(writer);
+ CompiledFileOutputStream outputStream(©ingAdaptor);
+
+ // Number of CompiledFiles.
+ outputStream.WriteLittleEndian32(1);
+
+ std::unique_ptr<pb::CompiledFile> compiledFile =
+ serializeCompiledFileToPb(file);
+ outputStream.WriteCompiledFile(compiledFile.get());
+ outputStream.WriteData(&buffer);
+
+ if (outputStream.HadError()) {
+ diag->error(DiagMessage(outputPath) << "failed to write data");
+ return false;
}
+ }
- // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
- {
- // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
- // interface.
- CopyingOutputStreamAdaptor copyingAdaptor(writer);
- CompiledFileOutputStream outputStream(©ingAdaptor);
-
- // Number of CompiledFiles.
- outputStream.WriteLittleEndian32(1);
-
- std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file);
- outputStream.WriteCompiledFile(compiledFile.get());
- outputStream.WriteData(&buffer);
-
- if (outputStream.HadError()) {
- diag->error(DiagMessage(outputPath) << "failed to write data");
- return false;
- }
- }
-
- if (!writer->finishEntry()) {
- diag->error(DiagMessage(outputPath) << "failed to finish writing data");
- return false;
- }
- return true;
+ if (!writer->finishEntry()) {
+ diag->error(DiagMessage(outputPath) << "failed to finish writing data");
+ return false;
+ }
+ return true;
}
-static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, const ResourceFile& file,
- const android::FileMap& map, IArchiveWriter* writer,
+static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath,
+ const ResourceFile& file,
+ const android::FileMap& map,
+ IArchiveWriter* writer,
IDiagnostics* diag) {
- // Start the entry so we can write the header.
- if (!writer->startEntry(outputPath, 0)) {
- diag->error(DiagMessage(outputPath) << "failed to open file");
- return false;
+ // Start the entry so we can write the header.
+ if (!writer->startEntry(outputPath, 0)) {
+ diag->error(DiagMessage(outputPath) << "failed to open file");
+ return false;
+ }
+
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call
+ // writer->finishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the
+ // ZeroCopyOutputStream
+ // interface.
+ CopyingOutputStreamAdaptor copyingAdaptor(writer);
+ CompiledFileOutputStream outputStream(©ingAdaptor);
+
+ // Number of CompiledFiles.
+ outputStream.WriteLittleEndian32(1);
+
+ std::unique_ptr<pb::CompiledFile> compiledFile =
+ serializeCompiledFileToPb(file);
+ outputStream.WriteCompiledFile(compiledFile.get());
+ outputStream.WriteData(map.getDataPtr(), map.getDataLength());
+
+ if (outputStream.HadError()) {
+ diag->error(DiagMessage(outputPath) << "failed to write data");
+ return false;
}
+ }
- // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
- {
- // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
- // interface.
- CopyingOutputStreamAdaptor copyingAdaptor(writer);
- CompiledFileOutputStream outputStream(©ingAdaptor);
-
- // Number of CompiledFiles.
- outputStream.WriteLittleEndian32(1);
-
- std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file);
- outputStream.WriteCompiledFile(compiledFile.get());
- outputStream.WriteData(map.getDataPtr(), map.getDataLength());
-
- if (outputStream.HadError()) {
- diag->error(DiagMessage(outputPath) << "failed to write data");
- return false;
- }
- }
-
- if (!writer->finishEntry()) {
- diag->error(DiagMessage(outputPath) << "failed to finish writing data");
- return false;
- }
- return true;
+ if (!writer->finishEntry()) {
+ diag->error(DiagMessage(outputPath) << "failed to finish writing data");
+ return false;
+ }
+ return true;
}
-static bool flattenXmlToOutStream(IAaptContext* context, const StringPiece& outputPath,
+static bool flattenXmlToOutStream(IAaptContext* context,
+ const StringPiece& outputPath,
xml::XmlResource* xmlRes,
CompiledFileOutputStream* out) {
- BigBuffer buffer(1024);
- XmlFlattenerOptions xmlFlattenerOptions;
- xmlFlattenerOptions.keepRawValues = true;
- XmlFlattener flattener(&buffer, xmlFlattenerOptions);
- if (!flattener.consume(context, xmlRes)) {
- return false;
- }
+ BigBuffer buffer(1024);
+ XmlFlattenerOptions xmlFlattenerOptions;
+ xmlFlattenerOptions.keepRawValues = true;
+ XmlFlattener flattener(&buffer, xmlFlattenerOptions);
+ if (!flattener.consume(context, xmlRes)) {
+ return false;
+ }
- std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(xmlRes->file);
- out->WriteCompiledFile(pbCompiledFile.get());
- out->WriteData(&buffer);
+ std::unique_ptr<pb::CompiledFile> pbCompiledFile =
+ serializeCompiledFileToPb(xmlRes->file);
+ out->WriteCompiledFile(pbCompiledFile.get());
+ out->WriteData(&buffer);
- if (out->HadError()) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write data");
- return false;
- }
- return true;
+ if (out->HadError()) {
+ context->getDiagnostics()->error(DiagMessage(outputPath)
+ << "failed to write data");
+ return false;
+ }
+ return true;
}
static bool compileXml(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& pathData, IArchiveWriter* writer,
const std::string& outputPath) {
- if (context->verbose()) {
- context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling XML");
+ if (context->verbose()) {
+ context->getDiagnostics()->note(DiagMessage(pathData.source)
+ << "compiling XML");
+ }
+
+ std::unique_ptr<xml::XmlResource> xmlRes;
+ {
+ std::ifstream fin(pathData.source.path, std::ifstream::binary);
+ if (!fin) {
+ context->getDiagnostics()->error(DiagMessage(pathData.source)
+ << strerror(errno));
+ return false;
}
- std::unique_ptr<xml::XmlResource> xmlRes;
- {
- std::ifstream fin(pathData.source.path, std::ifstream::binary);
- if (!fin) {
- context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
- return false;
- }
+ xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
- xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
+ fin.close();
+ }
- fin.close();
+ if (!xmlRes) {
+ return false;
+ }
+
+ xmlRes->file.name =
+ ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
+ xmlRes->file.config = pathData.config;
+ xmlRes->file.source = pathData.source;
+
+ // Collect IDs that are defined here.
+ XmlIdCollector collector;
+ if (!collector.consume(context, xmlRes.get())) {
+ return false;
+ }
+
+ // Look for and process any <aapt:attr> tags and create sub-documents.
+ InlineXmlFormatParser inlineXmlFormatParser;
+ if (!inlineXmlFormatParser.consume(context, xmlRes.get())) {
+ return false;
+ }
+
+ // Start the entry so we can write the header.
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath)
+ << "failed to open file");
+ return false;
+ }
+
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call
+ // writer->finishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the
+ // ZeroCopyOutputStream
+ // interface.
+ CopyingOutputStreamAdaptor copyingAdaptor(writer);
+ CompiledFileOutputStream outputStream(©ingAdaptor);
+
+ std::vector<std::unique_ptr<xml::XmlResource>>& inlineDocuments =
+ inlineXmlFormatParser.getExtractedInlineXmlDocuments();
+
+ // Number of CompiledFiles.
+ outputStream.WriteLittleEndian32(1 + inlineDocuments.size());
+
+ if (!flattenXmlToOutStream(context, outputPath, xmlRes.get(),
+ &outputStream)) {
+ return false;
}
- if (!xmlRes) {
+ for (auto& inlineXmlDoc : inlineDocuments) {
+ if (!flattenXmlToOutStream(context, outputPath, inlineXmlDoc.get(),
+ &outputStream)) {
return false;
+ }
}
+ }
- xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
- xmlRes->file.config = pathData.config;
- xmlRes->file.source = pathData.source;
-
- // Collect IDs that are defined here.
- XmlIdCollector collector;
- if (!collector.consume(context, xmlRes.get())) {
- return false;
- }
-
- // Look for and process any <aapt:attr> tags and create sub-documents.
- InlineXmlFormatParser inlineXmlFormatParser;
- if (!inlineXmlFormatParser.consume(context, xmlRes.get())) {
- return false;
- }
-
- // Start the entry so we can write the header.
- if (!writer->startEntry(outputPath, 0)) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open file");
- return false;
- }
-
- // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
- {
- // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
- // interface.
- CopyingOutputStreamAdaptor copyingAdaptor(writer);
- CompiledFileOutputStream outputStream(©ingAdaptor);
-
- std::vector<std::unique_ptr<xml::XmlResource>>& inlineDocuments =
- inlineXmlFormatParser.getExtractedInlineXmlDocuments();
-
- // Number of CompiledFiles.
- outputStream.WriteLittleEndian32(1 + inlineDocuments.size());
-
- if (!flattenXmlToOutStream(context, outputPath, xmlRes.get(), &outputStream)) {
- return false;
- }
-
- for (auto& inlineXmlDoc : inlineDocuments) {
- if (!flattenXmlToOutStream(context, outputPath, inlineXmlDoc.get(), &outputStream)) {
- return false;
- }
- }
- }
-
- if (!writer->finishEntry()) {
- context->getDiagnostics()->error(DiagMessage(outputPath)
- << "failed to finish writing data");
- return false;
- }
- return true;
+ if (!writer->finishEntry()) {
+ context->getDiagnostics()->error(DiagMessage(outputPath)
+ << "failed to finish writing data");
+ return false;
+ }
+ return true;
}
class BigBufferOutputStream : public io::OutputStream {
-public:
- explicit BigBufferOutputStream(BigBuffer* buffer) : mBuffer(buffer) {
- }
+ public:
+ explicit BigBufferOutputStream(BigBuffer* buffer) : mBuffer(buffer) {}
- bool Next(void** data, int* len) override {
- size_t count;
- *data = mBuffer->nextBlock(&count);
- *len = static_cast<int>(count);
- return true;
- }
+ bool Next(void** data, int* len) override {
+ size_t count;
+ *data = mBuffer->nextBlock(&count);
+ *len = static_cast<int>(count);
+ return true;
+ }
- void BackUp(int count) override {
- mBuffer->backUp(count);
- }
+ void BackUp(int count) override { mBuffer->backUp(count); }
- int64_t ByteCount() const override {
- return mBuffer->size();
- }
+ int64_t ByteCount() const override { return mBuffer->size(); }
- bool HadError() const override {
- return false;
- }
+ bool HadError() const override { return false; }
-private:
- BigBuffer* mBuffer;
+ private:
+ BigBuffer* mBuffer;
- DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
+ DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
};
static bool compilePng(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& pathData, IArchiveWriter* writer,
const std::string& outputPath) {
- if (context->verbose()) {
- context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling PNG");
+ if (context->verbose()) {
+ context->getDiagnostics()->note(DiagMessage(pathData.source)
+ << "compiling PNG");
+ }
+
+ BigBuffer buffer(4096);
+ ResourceFile resFile;
+ resFile.name =
+ ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
+ resFile.config = pathData.config;
+ resFile.source = pathData.source;
+
+ {
+ std::string content;
+ if (!android::base::ReadFileToString(pathData.source.path, &content)) {
+ context->getDiagnostics()->error(
+ DiagMessage(pathData.source)
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
}
- BigBuffer buffer(4096);
- ResourceFile resFile;
- resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
- resFile.config = pathData.config;
- resFile.source = pathData.source;
+ BigBuffer crunchedPngBuffer(4096);
+ BigBufferOutputStream crunchedPngBufferOut(&crunchedPngBuffer);
- {
- std::string content;
- if (!android::base::ReadFileToString(pathData.source.path, &content)) {
- context->getDiagnostics()->error(DiagMessage(pathData.source)
- << android::base::SystemErrorCodeToString(errno));
- return false;
- }
-
- BigBuffer crunchedPngBuffer(4096);
- BigBufferOutputStream crunchedPngBufferOut(&crunchedPngBuffer);
-
- // Ensure that we only keep the chunks we care about if we end up
- // using the original PNG instead of the crunched one.
- PngChunkFilter pngChunkFilter(content);
- std::unique_ptr<Image> image = readPng(context, &pngChunkFilter);
- if (!image) {
- return false;
- }
-
- std::unique_ptr<NinePatch> ninePatch;
- if (pathData.extension == "9.png") {
- std::string err;
- ninePatch = NinePatch::create(image->rows.get(), image->width, image->height, &err);
- if (!ninePatch) {
- context->getDiagnostics()->error(DiagMessage() << err);
- return false;
- }
-
- // Remove the 1px border around the NinePatch.
- // Basically the row array is shifted up by 1, and the length is treated
- // as height - 2.
- // For each row, shift the array to the left by 1, and treat the length as width - 2.
- image->width -= 2;
- image->height -= 2;
- memmove(image->rows.get(), image->rows.get() + 1, image->height * sizeof(uint8_t**));
- for (int32_t h = 0; h < image->height; h++) {
- memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
- }
-
- if (context->verbose()) {
- context->getDiagnostics()->note(DiagMessage(pathData.source)
- << "9-patch: " << *ninePatch);
- }
- }
-
- // Write the crunched PNG.
- if (!writePng(context, image.get(), ninePatch.get(), &crunchedPngBufferOut, {})) {
- return false;
- }
-
- if (ninePatch != nullptr
- || crunchedPngBufferOut.ByteCount() <= pngChunkFilter.ByteCount()) {
- // No matter what, we must use the re-encoded PNG, even if it is larger.
- // 9-patch images must be re-encoded since their borders are stripped.
- buffer.appendBuffer(std::move(crunchedPngBuffer));
- } else {
- // The re-encoded PNG is larger than the original, and there is
- // no mandatory transformation. Use the original.
- if (context->verbose()) {
- context->getDiagnostics()->note(DiagMessage(pathData.source)
- << "original PNG is smaller than crunched PNG"
- << ", using original");
- }
-
- PngChunkFilter pngChunkFilterAgain(content);
- BigBuffer filteredPngBuffer(4096);
- BigBufferOutputStream filteredPngBufferOut(&filteredPngBuffer);
- io::copy(&filteredPngBufferOut, &pngChunkFilterAgain);
- buffer.appendBuffer(std::move(filteredPngBuffer));
- }
-
- if (context->verbose()) {
- // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
- // This will help catch exotic cases where the new code may generate larger PNGs.
- std::stringstream legacyStream(content);
- BigBuffer legacyBuffer(4096);
- Png png(context->getDiagnostics());
- if (!png.process(pathData.source, &legacyStream, &legacyBuffer, {})) {
- return false;
- }
-
- context->getDiagnostics()->note(DiagMessage(pathData.source)
- << "legacy=" << legacyBuffer.size()
- << " new=" << buffer.size());
- }
+ // Ensure that we only keep the chunks we care about if we end up
+ // using the original PNG instead of the crunched one.
+ PngChunkFilter pngChunkFilter(content);
+ std::unique_ptr<Image> image = readPng(context, &pngChunkFilter);
+ if (!image) {
+ return false;
}
- if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer,
- context->getDiagnostics())) {
+ std::unique_ptr<NinePatch> ninePatch;
+ if (pathData.extension == "9.png") {
+ std::string err;
+ ninePatch = NinePatch::create(image->rows.get(), image->width,
+ image->height, &err);
+ if (!ninePatch) {
+ context->getDiagnostics()->error(DiagMessage() << err);
return false;
+ }
+
+ // Remove the 1px border around the NinePatch.
+ // Basically the row array is shifted up by 1, and the length is treated
+ // as height - 2.
+ // For each row, shift the array to the left by 1, and treat the length as
+ // width - 2.
+ image->width -= 2;
+ image->height -= 2;
+ memmove(image->rows.get(), image->rows.get() + 1,
+ image->height * sizeof(uint8_t**));
+ for (int32_t h = 0; h < image->height; h++) {
+ memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
+ }
+
+ if (context->verbose()) {
+ context->getDiagnostics()->note(DiagMessage(pathData.source)
+ << "9-patch: " << *ninePatch);
+ }
}
- return true;
+
+ // Write the crunched PNG.
+ if (!writePng(context, image.get(), ninePatch.get(), &crunchedPngBufferOut,
+ {})) {
+ return false;
+ }
+
+ if (ninePatch != nullptr ||
+ crunchedPngBufferOut.ByteCount() <= pngChunkFilter.ByteCount()) {
+ // No matter what, we must use the re-encoded PNG, even if it is larger.
+ // 9-patch images must be re-encoded since their borders are stripped.
+ buffer.appendBuffer(std::move(crunchedPngBuffer));
+ } else {
+ // The re-encoded PNG is larger than the original, and there is
+ // no mandatory transformation. Use the original.
+ if (context->verbose()) {
+ context->getDiagnostics()->note(
+ DiagMessage(pathData.source)
+ << "original PNG is smaller than crunched PNG"
+ << ", using original");
+ }
+
+ PngChunkFilter pngChunkFilterAgain(content);
+ BigBuffer filteredPngBuffer(4096);
+ BigBufferOutputStream filteredPngBufferOut(&filteredPngBuffer);
+ io::copy(&filteredPngBufferOut, &pngChunkFilterAgain);
+ buffer.appendBuffer(std::move(filteredPngBuffer));
+ }
+
+ if (context->verbose()) {
+ // For debugging only, use the legacy PNG cruncher and compare the
+ // resulting file sizes.
+ // This will help catch exotic cases where the new code may generate
+ // larger PNGs.
+ std::stringstream legacyStream(content);
+ BigBuffer legacyBuffer(4096);
+ Png png(context->getDiagnostics());
+ if (!png.process(pathData.source, &legacyStream, &legacyBuffer, {})) {
+ return false;
+ }
+
+ context->getDiagnostics()->note(DiagMessage(pathData.source)
+ << "legacy=" << legacyBuffer.size()
+ << " new=" << buffer.size());
+ }
+ }
+
+ if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer,
+ context->getDiagnostics())) {
+ return false;
+ }
+ return true;
}
static bool compileFile(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, IArchiveWriter* writer,
- const std::string& outputPath) {
- if (context->verbose()) {
- context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling file");
- }
+ const ResourcePathData& pathData,
+ IArchiveWriter* writer, const std::string& outputPath) {
+ if (context->verbose()) {
+ context->getDiagnostics()->note(DiagMessage(pathData.source)
+ << "compiling file");
+ }
- BigBuffer buffer(256);
- ResourceFile resFile;
- resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
- resFile.config = pathData.config;
- resFile.source = pathData.source;
+ BigBuffer buffer(256);
+ ResourceFile resFile;
+ resFile.name =
+ ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
+ resFile.config = pathData.config;
+ resFile.source = pathData.source;
- std::string errorStr;
- Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
- if (!f) {
- context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
- return false;
- }
+ std::string errorStr;
+ Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
+ if (!f) {
+ context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
+ return false;
+ }
- if (!writeHeaderAndMmapToWriter(outputPath, resFile, f.value(), writer,
- context->getDiagnostics())) {
- return false;
- }
- return true;
+ if (!writeHeaderAndMmapToWriter(outputPath, resFile, f.value(), writer,
+ context->getDiagnostics())) {
+ return false;
+ }
+ return true;
}
class CompileContext : public IAaptContext {
-public:
- void setVerbose(bool val) {
- mVerbose = val;
- }
+ public:
+ void setVerbose(bool val) { mVerbose = val; }
- bool verbose() override {
- return mVerbose;
- }
+ bool verbose() override { return mVerbose; }
- IDiagnostics* getDiagnostics() override {
- return &mDiagnostics;
- }
+ IDiagnostics* getDiagnostics() override { return &mDiagnostics; }
- NameMangler* getNameMangler() override {
- abort();
- return nullptr;
- }
+ NameMangler* getNameMangler() override {
+ abort();
+ return nullptr;
+ }
- const std::string& getCompilationPackage() override {
- static std::string empty;
- return empty;
- }
+ const std::string& getCompilationPackage() override {
+ static std::string empty;
+ return empty;
+ }
- uint8_t getPackageId() override {
- return 0x0;
- }
+ uint8_t getPackageId() override { return 0x0; }
- SymbolTable* getExternalSymbols() override {
- abort();
- return nullptr;
- }
+ SymbolTable* getExternalSymbols() override {
+ abort();
+ return nullptr;
+ }
- int getMinSdkVersion() override {
- return 0;
- }
+ int getMinSdkVersion() override { return 0; }
-private:
- StdErrDiagnostics mDiagnostics;
- bool mVerbose = false;
-
+ private:
+ StdErrDiagnostics mDiagnostics;
+ bool mVerbose = false;
};
/**
- * Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
+ * Entry point for compilation phase. Parses arguments and dispatches to the
+ * correct steps.
*/
int compile(const std::vector<StringPiece>& args) {
- CompileContext context;
- CompileOptions options;
+ CompileContext context;
+ CompileOptions options;
- bool verbose = false;
- Flags flags = Flags()
- .requiredFlag("-o", "Output path", &options.outputPath)
- .optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
- .optionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales "
- "(en-XA and ar-XB)", &options.pseudolocalize)
- .optionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
- &options.legacyMode)
- .optionalSwitch("-v", "Enables verbose logging", &verbose);
- if (!flags.parse("aapt2 compile", args, &std::cerr)) {
- return 1;
+ bool verbose = false;
+ Flags flags =
+ Flags()
+ .requiredFlag("-o", "Output path", &options.outputPath)
+ .optionalFlag("--dir", "Directory to scan for resources",
+ &options.resDir)
+ .optionalSwitch("--pseudo-localize",
+ "Generate resources for pseudo-locales "
+ "(en-XA and ar-XB)",
+ &options.pseudolocalize)
+ .optionalSwitch(
+ "--legacy",
+ "Treat errors that used to be valid in AAPT as warnings",
+ &options.legacyMode)
+ .optionalSwitch("-v", "Enables verbose logging", &verbose);
+ if (!flags.parse("aapt2 compile", args, &std::cerr)) {
+ return 1;
+ }
+
+ context.setVerbose(verbose);
+
+ std::unique_ptr<IArchiveWriter> archiveWriter;
+
+ std::vector<ResourcePathData> inputData;
+ if (options.resDir) {
+ if (!flags.getArgs().empty()) {
+ // Can't have both files and a resource directory.
+ context.getDiagnostics()->error(DiagMessage()
+ << "files given but --dir specified");
+ flags.usage("aapt2 compile", &std::cerr);
+ return 1;
}
- context.setVerbose(verbose);
+ if (!loadInputFilesFromDir(&context, options, &inputData)) {
+ return 1;
+ }
- std::unique_ptr<IArchiveWriter> archiveWriter;
+ archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(),
+ options.outputPath);
- std::vector<ResourcePathData> inputData;
- if (options.resDir) {
- if (!flags.getArgs().empty()) {
- // Can't have both files and a resource directory.
- context.getDiagnostics()->error(DiagMessage() << "files given but --dir specified");
- flags.usage("aapt2 compile", &std::cerr);
- return 1;
- }
+ } else {
+ inputData.reserve(flags.getArgs().size());
- if (!loadInputFilesFromDir(&context, options, &inputData)) {
- return 1;
- }
+ // Collect data from the path for each input file.
+ for (const std::string& arg : flags.getArgs()) {
+ std::string errorStr;
+ if (Maybe<ResourcePathData> pathData =
+ extractResourcePathData(arg, &errorStr)) {
+ inputData.push_back(std::move(pathData.value()));
+ } else {
+ context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg
+ << ")");
+ return 1;
+ }
+ }
- archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath);
+ archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(),
+ options.outputPath);
+ }
+
+ if (!archiveWriter) {
+ return 1;
+ }
+
+ bool error = false;
+ for (ResourcePathData& pathData : inputData) {
+ if (options.verbose) {
+ context.getDiagnostics()->note(DiagMessage(pathData.source)
+ << "processing");
+ }
+
+ if (pathData.resourceDir == "values") {
+ // Overwrite the extension.
+ pathData.extension = "arsc";
+
+ const std::string outputFilename = buildIntermediateFilename(pathData);
+ if (!compileTable(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
+ error = true;
+ }
} else {
- inputData.reserve(flags.getArgs().size());
-
- // Collect data from the path for each input file.
- for (const std::string& arg : flags.getArgs()) {
- std::string errorStr;
- if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
- inputData.push_back(std::move(pathData.value()));
- } else {
- context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
- return 1;
+ const std::string outputFilename = buildIntermediateFilename(pathData);
+ if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
+ if (*type != ResourceType::kRaw) {
+ if (pathData.extension == "xml") {
+ if (!compileXml(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
+ error = true;
}
- }
-
- archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(), options.outputPath);
- }
-
- if (!archiveWriter) {
- return false;
- }
-
- bool error = false;
- for (ResourcePathData& pathData : inputData) {
- if (options.verbose) {
- context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing");
- }
-
- if (pathData.resourceDir == "values") {
- // Overwrite the extension.
- pathData.extension = "arsc";
-
- const std::string outputFilename = buildIntermediateFilename(pathData);
- if (!compileTable(&context, options, pathData, archiveWriter.get(), outputFilename)) {
- error = true;
+ } else if (pathData.extension == "png" ||
+ pathData.extension == "9.png") {
+ if (!compilePng(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
+ error = true;
}
-
+ } else {
+ if (!compileFile(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
+ error = true;
+ }
+ }
} else {
- const std::string outputFilename = buildIntermediateFilename(pathData);
- if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
- if (*type != ResourceType::kRaw) {
- if (pathData.extension == "xml") {
- if (!compileXml(&context, options, pathData, archiveWriter.get(),
- outputFilename)) {
- error = true;
- }
- } else if (pathData.extension == "png" || pathData.extension == "9.png") {
- if (!compilePng(&context, options, pathData, archiveWriter.get(),
- outputFilename)) {
- error = true;
- }
- } else {
- if (!compileFile(&context, options, pathData, archiveWriter.get(),
- outputFilename)) {
- error = true;
- }
- }
- } else {
- if (!compileFile(&context, options, pathData, archiveWriter.get(),
- outputFilename)) {
- error = true;
- }
- }
- } else {
- context.getDiagnostics()->error(
- DiagMessage() << "invalid file path '" << pathData.source << "'");
- error = true;
- }
+ if (!compileFile(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
+ error = true;
+ }
}
+ } else {
+ context.getDiagnostics()->error(
+ DiagMessage() << "invalid file path '" << pathData.source << "'");
+ error = true;
+ }
}
+ }
- if (error) {
- return 1;
- }
- return 0;
+ if (error) {
+ return 1;
+ }
+ return 0;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
index 4a3f1e1..73eb066 100644
--- a/tools/aapt2/compile/IdAssigner.cpp
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#include "ResourceTable.h"
#include "compile/IdAssigner.h"
+#include "ResourceTable.h"
#include "process/IResourceTableConsumer.h"
#include "util/Util.h"
@@ -25,178 +25,192 @@
namespace aapt {
/**
- * Assigns the intended ID to the ResourceTablePackage, ResourceTableType, and ResourceEntry,
+ * Assigns the intended ID to the ResourceTablePackage, ResourceTableType, and
+ * ResourceEntry,
* as long as there is no existing ID or the ID is the same.
*/
-static bool assignId(IDiagnostics* diag, const ResourceId& id, const ResourceName& name,
- ResourceTablePackage* pkg, ResourceTableType* type, ResourceEntry* entry) {
- if (pkg->id.value() == id.packageId()) {
- if (!type->id || type->id.value() == id.typeId()) {
- type->id = id.typeId();
+static bool assignId(IDiagnostics* diag, const ResourceId& id,
+ const ResourceName& name, ResourceTablePackage* pkg,
+ ResourceTableType* type, ResourceEntry* entry) {
+ if (pkg->id.value() == id.packageId()) {
+ if (!type->id || type->id.value() == id.typeId()) {
+ type->id = id.typeId();
- if (!entry->id || entry->id.value() == id.entryId()) {
- entry->id = id.entryId();
- return true;
- }
- }
+ if (!entry->id || entry->id.value() == id.entryId()) {
+ entry->id = id.entryId();
+ return true;
+ }
}
+ }
- const ResourceId existingId(pkg->id.value(),
- type->id ? type->id.value() : 0,
- entry->id ? entry->id.value() : 0);
- diag->error(DiagMessage() << "can't assign ID " << id
- << " to resource " << name
- << " with conflicting ID " << existingId);
- return false;
+ const ResourceId existingId(pkg->id.value(), type->id ? type->id.value() : 0,
+ entry->id ? entry->id.value() : 0);
+ diag->error(DiagMessage() << "can't assign ID " << id << " to resource "
+ << name << " with conflicting ID " << existingId);
+ return false;
}
bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
- std::map<ResourceId, ResourceName> assignedIds;
+ std::map<ResourceId, ResourceName> assignedIds;
- for (auto& package : table->packages) {
- assert(package->id && "packages must have manually assigned IDs");
+ for (auto& package : table->packages) {
+ assert(package->id && "packages must have manually assigned IDs");
- for (auto& type : package->types) {
- for (auto& entry : type->entries) {
- const ResourceName name(package->name, type->type, entry->name);
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ const ResourceName name(package->name, type->type, entry->name);
- if (mAssignedIdMap) {
- // Assign the pre-assigned stable ID meant for this resource.
- const auto iter = mAssignedIdMap->find(name);
- if (iter != mAssignedIdMap->end()) {
- const ResourceId assignedId = iter->second;
- const bool result = assignId(context->getDiagnostics(), assignedId, name,
- package.get(), type.get(), entry.get());
- if (!result) {
- return false;
- }
- }
- }
-
- if (package->id && type->id && entry->id) {
- // If the ID is set for this resource, then reserve it.
- ResourceId resourceId(package->id.value(), type->id.value(), entry->id.value());
- auto result = assignedIds.insert({ resourceId, name });
- const ResourceName& existingName = result.first->second;
- if (!result.second) {
- context->getDiagnostics()->error(DiagMessage() << "resource " << name
- << " has same ID "
- << resourceId
- << " as " << existingName);
- return false;
- }
- }
+ if (mAssignedIdMap) {
+ // Assign the pre-assigned stable ID meant for this resource.
+ const auto iter = mAssignedIdMap->find(name);
+ if (iter != mAssignedIdMap->end()) {
+ const ResourceId assignedId = iter->second;
+ const bool result =
+ assignId(context->getDiagnostics(), assignedId, name,
+ package.get(), type.get(), entry.get());
+ if (!result) {
+ return false;
}
+ }
}
- }
- if (mAssignedIdMap) {
- // Reserve all the IDs mentioned in the stable ID map. That way we won't assign
- // IDs that were listed in the map if they don't exist in the table.
- for (const auto& stableIdEntry : *mAssignedIdMap) {
- const ResourceName& preAssignedName = stableIdEntry.first;
- const ResourceId& preAssignedId = stableIdEntry.second;
- auto result = assignedIds.insert({ preAssignedId, preAssignedName });
- const ResourceName& existingName = result.first->second;
- if (!result.second && existingName != preAssignedName) {
- context->getDiagnostics()->error(DiagMessage() << "stable ID " << preAssignedId
- << " for resource " << preAssignedName
- << " is already taken by resource "
- << existingName);
- return false;
- }
+ if (package->id && type->id && entry->id) {
+ // If the ID is set for this resource, then reserve it.
+ ResourceId resourceId(package->id.value(), type->id.value(),
+ entry->id.value());
+ auto result = assignedIds.insert({resourceId, name});
+ const ResourceName& existingName = result.first->second;
+ if (!result.second) {
+ context->getDiagnostics()->error(
+ DiagMessage() << "resource " << name << " has same ID "
+ << resourceId << " as " << existingName);
+ return false;
+ }
}
+ }
}
+ }
- // Assign any resources without IDs the next available ID. Gaps will be filled if possible,
- // unless those IDs have been reserved.
+ if (mAssignedIdMap) {
+ // Reserve all the IDs mentioned in the stable ID map. That way we won't
+ // assign
+ // IDs that were listed in the map if they don't exist in the table.
+ for (const auto& stableIdEntry : *mAssignedIdMap) {
+ const ResourceName& preAssignedName = stableIdEntry.first;
+ const ResourceId& preAssignedId = stableIdEntry.second;
+ auto result = assignedIds.insert({preAssignedId, preAssignedName});
+ const ResourceName& existingName = result.first->second;
+ if (!result.second && existingName != preAssignedName) {
+ context->getDiagnostics()->error(
+ DiagMessage() << "stable ID " << preAssignedId << " for resource "
+ << preAssignedName << " is already taken by resource "
+ << existingName);
+ return false;
+ }
+ }
+ }
- const auto assignedIdsIterEnd = assignedIds.end();
- for (auto& package : table->packages) {
- assert(package->id && "packages must have manually assigned IDs");
+ // Assign any resources without IDs the next available ID. Gaps will be filled
+ // if possible,
+ // unless those IDs have been reserved.
- // Build a half filled ResourceId object, which will be used to find the closest matching
- // reserved ID in the assignedId map. From that point the next available type ID can be
- // found.
- ResourceId resourceId(package->id.value(), 0, 0);
- uint8_t nextExpectedTypeId = 1;
+ const auto assignedIdsIterEnd = assignedIds.end();
+ for (auto& package : table->packages) {
+ assert(package->id && "packages must have manually assigned IDs");
- // Find the closest matching ResourceId that is <= the one with only the package set.
- auto nextTypeIter = assignedIds.lower_bound(resourceId);
- for (auto& type : package->types) {
- if (!type->id) {
- // We need to assign a type ID. Iterate over the reserved IDs until we find
- // some type ID that is a distance of 2 greater than the last one we've seen.
- // That means there is an available type ID between these reserved IDs.
- while (nextTypeIter != assignedIdsIterEnd) {
- if (nextTypeIter->first.packageId() != package->id.value()) {
- break;
- }
+ // Build a half filled ResourceId object, which will be used to find the
+ // closest matching
+ // reserved ID in the assignedId map. From that point the next available
+ // type ID can be
+ // found.
+ ResourceId resourceId(package->id.value(), 0, 0);
+ uint8_t nextExpectedTypeId = 1;
- const uint8_t typeId = nextTypeIter->first.typeId();
- if (typeId > nextExpectedTypeId) {
- // There is a gap in the type IDs, so use the missing one.
- type->id = nextExpectedTypeId++;
- break;
- }
+ // Find the closest matching ResourceId that is <= the one with only the
+ // package set.
+ auto nextTypeIter = assignedIds.lower_bound(resourceId);
+ for (auto& type : package->types) {
+ if (!type->id) {
+ // We need to assign a type ID. Iterate over the reserved IDs until we
+ // find
+ // some type ID that is a distance of 2 greater than the last one we've
+ // seen.
+ // That means there is an available type ID between these reserved IDs.
+ while (nextTypeIter != assignedIdsIterEnd) {
+ if (nextTypeIter->first.packageId() != package->id.value()) {
+ break;
+ }
- // Set our expectation to be the next type ID after the reserved one we
- // just saw.
- nextExpectedTypeId = typeId + 1;
+ const uint8_t typeId = nextTypeIter->first.typeId();
+ if (typeId > nextExpectedTypeId) {
+ // There is a gap in the type IDs, so use the missing one.
+ type->id = nextExpectedTypeId++;
+ break;
+ }
- // Move to the next reserved ID.
- ++nextTypeIter;
- }
+ // Set our expectation to be the next type ID after the reserved one
+ // we
+ // just saw.
+ nextExpectedTypeId = typeId + 1;
- if (!type->id) {
- // We must have hit the end of the reserved IDs and not found a gap.
- // That means the next ID is available.
- type->id = nextExpectedTypeId++;
- }
- }
-
- resourceId = ResourceId(package->id.value(), type->id.value(), 0);
- uint16_t nextExpectedEntryId = 0;
-
- // Find the closest matching ResourceId that is <= the one with only the package
- // and type set.
- auto nextEntryIter = assignedIds.lower_bound(resourceId);
- for (auto& entry : type->entries) {
- if (!entry->id) {
- // We need to assign an entry ID. Iterate over the reserved IDs until we find
- // some entry ID that is a distance of 2 greater than the last one we've seen.
- // That means there is an available entry ID between these reserved IDs.
- while (nextEntryIter != assignedIdsIterEnd) {
- if (nextEntryIter->first.packageId() != package->id.value() ||
- nextEntryIter->first.typeId() != type->id.value()) {
- break;
- }
-
- const uint16_t entryId = nextEntryIter->first.entryId();
- if (entryId > nextExpectedEntryId) {
- // There is a gap in the entry IDs, so use the missing one.
- entry->id = nextExpectedEntryId++;
- break;
- }
-
- // Set our expectation to be the next type ID after the reserved one we
- // just saw.
- nextExpectedEntryId = entryId + 1;
-
- // Move to the next reserved entry ID.
- ++nextEntryIter;
- }
-
- if (!entry->id) {
- // We must have hit the end of the reserved IDs and not found a gap.
- // That means the next ID is available.
- entry->id = nextExpectedEntryId++;
- }
- }
- }
+ // Move to the next reserved ID.
+ ++nextTypeIter;
}
+
+ if (!type->id) {
+ // We must have hit the end of the reserved IDs and not found a gap.
+ // That means the next ID is available.
+ type->id = nextExpectedTypeId++;
+ }
+ }
+
+ resourceId = ResourceId(package->id.value(), type->id.value(), 0);
+ uint16_t nextExpectedEntryId = 0;
+
+ // Find the closest matching ResourceId that is <= the one with only the
+ // package
+ // and type set.
+ auto nextEntryIter = assignedIds.lower_bound(resourceId);
+ for (auto& entry : type->entries) {
+ if (!entry->id) {
+ // We need to assign an entry ID. Iterate over the reserved IDs until
+ // we find
+ // some entry ID that is a distance of 2 greater than the last one
+ // we've seen.
+ // That means there is an available entry ID between these reserved
+ // IDs.
+ while (nextEntryIter != assignedIdsIterEnd) {
+ if (nextEntryIter->first.packageId() != package->id.value() ||
+ nextEntryIter->first.typeId() != type->id.value()) {
+ break;
+ }
+
+ const uint16_t entryId = nextEntryIter->first.entryId();
+ if (entryId > nextExpectedEntryId) {
+ // There is a gap in the entry IDs, so use the missing one.
+ entry->id = nextExpectedEntryId++;
+ break;
+ }
+
+ // Set our expectation to be the next type ID after the reserved one
+ // we
+ // just saw.
+ nextExpectedEntryId = entryId + 1;
+
+ // Move to the next reserved entry ID.
+ ++nextEntryIter;
+ }
+
+ if (!entry->id) {
+ // We must have hit the end of the reserved IDs and not found a gap.
+ // That means the next ID is available.
+ entry->id = nextExpectedEntryId++;
+ }
+ }
+ }
}
- return true;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/IdAssigner.h b/tools/aapt2/compile/IdAssigner.h
index 06cd5e3..d399064 100644
--- a/tools/aapt2/compile/IdAssigner.h
+++ b/tools/aapt2/compile/IdAssigner.h
@@ -26,22 +26,22 @@
namespace aapt {
/**
- * Assigns IDs to each resource in the table, respecting existing IDs and filling in gaps
+ * Assigns IDs to each resource in the table, respecting existing IDs and
+ * filling in gaps
* in between fixed ID assignments.
*/
class IdAssigner : public IResourceTableConsumer {
-public:
- IdAssigner() = default;
- explicit IdAssigner(const std::unordered_map<ResourceName, ResourceId>* map) :
- mAssignedIdMap(map) {
- }
+ public:
+ IdAssigner() = default;
+ explicit IdAssigner(const std::unordered_map<ResourceName, ResourceId>* map)
+ : mAssignedIdMap(map) {}
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ bool consume(IAaptContext* context, ResourceTable* table) override;
-private:
- const std::unordered_map<ResourceName, ResourceId>* mAssignedIdMap = nullptr;
+ private:
+ const std::unordered_map<ResourceName, ResourceId>* mAssignedIdMap = nullptr;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_COMPILE_IDASSIGNER_H */
diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp
index d21fcba..ff7bf5c 100644
--- a/tools/aapt2/compile/IdAssigner_test.cpp
+++ b/tools/aapt2/compile/IdAssigner_test.cpp
@@ -22,154 +22,163 @@
::testing::AssertionResult verifyIds(ResourceTable* table);
TEST(IdAssignerTest, AssignIds) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addSimple("android:attr/foo")
- .addSimple("android:attr/bar")
- .addSimple("android:id/foo")
- .setPackageId("android", 0x01)
- .build();
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .addSimple("android:attr/foo")
+ .addSimple("android:attr/bar")
+ .addSimple("android:id/foo")
+ .setPackageId("android", 0x01)
+ .build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- IdAssigner assigner;
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ IdAssigner assigner;
- ASSERT_TRUE(assigner.consume(context.get(), table.get()));
- ASSERT_TRUE(verifyIds(table.get()));
+ ASSERT_TRUE(assigner.consume(context.get(), table.get()));
+ ASSERT_TRUE(verifyIds(table.get()));
}
TEST(IdAssignerTest, AssignIdsWithReservedIds) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addSimple("android:id/foo", ResourceId(0x01010000))
- .addSimple("android:dimen/two")
- .addSimple("android:integer/three")
- .addSimple("android:string/five")
- .addSimple("android:attr/fun", ResourceId(0x01040000))
- .addSimple("android:attr/foo", ResourceId(0x01040006))
- .addSimple("android:attr/bar")
- .addSimple("android:attr/baz")
- .addSimple("app:id/biz")
- .setPackageId("android", 0x01)
- .setPackageId("app", 0x7f)
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .addSimple("android:id/foo", ResourceId(0x01010000))
+ .addSimple("android:dimen/two")
+ .addSimple("android:integer/three")
+ .addSimple("android:string/five")
+ .addSimple("android:attr/fun", ResourceId(0x01040000))
+ .addSimple("android:attr/foo", ResourceId(0x01040006))
+ .addSimple("android:attr/bar")
+ .addSimple("android:attr/baz")
+ .addSimple("app:id/biz")
+ .setPackageId("android", 0x01)
+ .setPackageId("app", 0x7f)
+ .build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- IdAssigner assigner;
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ IdAssigner assigner;
- ASSERT_TRUE(assigner.consume(context.get(), table.get()));
- ASSERT_TRUE(verifyIds(table.get()));
+ ASSERT_TRUE(assigner.consume(context.get(), table.get()));
+ ASSERT_TRUE(verifyIds(table.get()));
- Maybe<ResourceTable::SearchResult> maybeResult;
+ Maybe<ResourceTable::SearchResult> maybeResult;
- // Expect to fill in the gaps between 0x0101XXXX and 0x0104XXXX.
+ // Expect to fill in the gaps between 0x0101XXXX and 0x0104XXXX.
- maybeResult = table->findResource(test::parseNameOrDie("android:dimen/two"));
- AAPT_ASSERT_TRUE(maybeResult);
- EXPECT_EQ(make_value<uint8_t>(2), maybeResult.value().type->id);
+ maybeResult = table->findResource(test::parseNameOrDie("android:dimen/two"));
+ AAPT_ASSERT_TRUE(maybeResult);
+ EXPECT_EQ(make_value<uint8_t>(2), maybeResult.value().type->id);
- maybeResult = table->findResource(test::parseNameOrDie("android:integer/three"));
- AAPT_ASSERT_TRUE(maybeResult);
- EXPECT_EQ(make_value<uint8_t>(3), maybeResult.value().type->id);
+ maybeResult =
+ table->findResource(test::parseNameOrDie("android:integer/three"));
+ AAPT_ASSERT_TRUE(maybeResult);
+ EXPECT_EQ(make_value<uint8_t>(3), maybeResult.value().type->id);
- // Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX IDs.
+ // Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX
+ // IDs.
- maybeResult = table->findResource(test::parseNameOrDie("android:string/five"));
- AAPT_ASSERT_TRUE(maybeResult);
- EXPECT_EQ(make_value<uint8_t>(5), maybeResult.value().type->id);
+ maybeResult =
+ table->findResource(test::parseNameOrDie("android:string/five"));
+ AAPT_ASSERT_TRUE(maybeResult);
+ EXPECT_EQ(make_value<uint8_t>(5), maybeResult.value().type->id);
- // Expect to fill in the gaps between 0x01040000 and 0x01040006.
+ // Expect to fill in the gaps between 0x01040000 and 0x01040006.
- maybeResult = table->findResource(test::parseNameOrDie("android:attr/bar"));
- AAPT_ASSERT_TRUE(maybeResult);
- EXPECT_EQ(make_value<uint16_t>(1), maybeResult.value().entry->id);
+ maybeResult = table->findResource(test::parseNameOrDie("android:attr/bar"));
+ AAPT_ASSERT_TRUE(maybeResult);
+ EXPECT_EQ(make_value<uint16_t>(1), maybeResult.value().entry->id);
- maybeResult = table->findResource(test::parseNameOrDie("android:attr/baz"));
- AAPT_ASSERT_TRUE(maybeResult);
- EXPECT_EQ(make_value<uint16_t>(2), maybeResult.value().entry->id);
+ maybeResult = table->findResource(test::parseNameOrDie("android:attr/baz"));
+ AAPT_ASSERT_TRUE(maybeResult);
+ EXPECT_EQ(make_value<uint16_t>(2), maybeResult.value().entry->id);
}
TEST(IdAssignerTest, FailWhenNonUniqueIdsAssigned) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addSimple("android:attr/foo", ResourceId(0x01040006))
- .addSimple("android:attr/bar", ResourceId(0x01040006))
- .setPackageId("android", 0x01)
- .setPackageId("app", 0x7f)
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .addSimple("android:attr/foo", ResourceId(0x01040006))
+ .addSimple("android:attr/bar", ResourceId(0x01040006))
+ .setPackageId("android", 0x01)
+ .setPackageId("app", 0x7f)
+ .build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- IdAssigner assigner;
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ IdAssigner assigner;
- ASSERT_FALSE(assigner.consume(context.get(), table.get()));
+ ASSERT_FALSE(assigner.consume(context.get(), table.get()));
}
TEST(IdAssignerTest, AssignIdsWithIdMap) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addSimple("android:attr/foo")
- .addSimple("android:attr/bar")
- .setPackageId("android", 0x01)
- .build();
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .addSimple("android:attr/foo")
+ .addSimple("android:attr/bar")
+ .setPackageId("android", 0x01)
+ .build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unordered_map<ResourceName, ResourceId> idMap = {
- { test::parseNameOrDie("android:attr/foo"), ResourceId(0x01010002) } };
- IdAssigner assigner(&idMap);
- ASSERT_TRUE(assigner.consume(context.get(), table.get()));
- ASSERT_TRUE(verifyIds(table.get()));
- Maybe<ResourceTable::SearchResult> result = table->findResource(
- test::parseNameOrDie("android:attr/foo"));
- AAPT_ASSERT_TRUE(result);
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unordered_map<ResourceName, ResourceId> idMap = {
+ {test::parseNameOrDie("android:attr/foo"), ResourceId(0x01010002)}};
+ IdAssigner assigner(&idMap);
+ ASSERT_TRUE(assigner.consume(context.get(), table.get()));
+ ASSERT_TRUE(verifyIds(table.get()));
+ Maybe<ResourceTable::SearchResult> result =
+ table->findResource(test::parseNameOrDie("android:attr/foo"));
+ AAPT_ASSERT_TRUE(result);
- const ResourceTable::SearchResult& searchResult = result.value();
- EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.package->id);
- EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.type->id);
- EXPECT_EQ(make_value<uint16_t>(0x0002), searchResult.entry->id);
+ const ResourceTable::SearchResult& searchResult = result.value();
+ EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.package->id);
+ EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.type->id);
+ EXPECT_EQ(make_value<uint16_t>(0x0002), searchResult.entry->id);
}
::testing::AssertionResult verifyIds(ResourceTable* table) {
- std::set<uint8_t> packageIds;
- for (auto& package : table->packages) {
- if (!package->id) {
- return ::testing::AssertionFailure() << "package " << package->name << " has no ID";
- }
-
- if (!packageIds.insert(package->id.value()).second) {
- return ::testing::AssertionFailure() << "package " << package->name
- << " has non-unique ID " << std::hex << (int) package->id.value() << std::dec;
- }
+ std::set<uint8_t> packageIds;
+ for (auto& package : table->packages) {
+ if (!package->id) {
+ return ::testing::AssertionFailure() << "package " << package->name
+ << " has no ID";
}
- for (auto& package : table->packages) {
- std::set<uint8_t> typeIds;
- for (auto& type : package->types) {
- if (!type->id) {
- return ::testing::AssertionFailure() << "type " << type->type << " of package "
- << package->name << " has no ID";
- }
-
- if (!typeIds.insert(type->id.value()).second) {
- return ::testing::AssertionFailure() << "type " << type->type
- << " of package " << package->name << " has non-unique ID "
- << std::hex << (int) type->id.value() << std::dec;
- }
- }
-
-
- for (auto& type : package->types) {
- std::set<uint16_t> entryIds;
- for (auto& entry : type->entries) {
- if (!entry->id) {
- return ::testing::AssertionFailure() << "entry " << entry->name << " of type "
- << type->type << " of package " << package->name << " has no ID";
- }
-
- if (!entryIds.insert(entry->id.value()).second) {
- return ::testing::AssertionFailure() << "entry " << entry->name
- << " of type " << type->type << " of package " << package->name
- << " has non-unique ID "
- << std::hex << (int) entry->id.value() << std::dec;
- }
- }
- }
+ if (!packageIds.insert(package->id.value()).second) {
+ return ::testing::AssertionFailure()
+ << "package " << package->name << " has non-unique ID " << std::hex
+ << (int)package->id.value() << std::dec;
}
- return ::testing::AssertionSuccess() << "all IDs are unique and assigned";
+ }
+
+ for (auto& package : table->packages) {
+ std::set<uint8_t> typeIds;
+ for (auto& type : package->types) {
+ if (!type->id) {
+ return ::testing::AssertionFailure() << "type " << type->type
+ << " of package " << package->name
+ << " has no ID";
+ }
+
+ if (!typeIds.insert(type->id.value()).second) {
+ return ::testing::AssertionFailure()
+ << "type " << type->type << " of package " << package->name
+ << " has non-unique ID " << std::hex << (int)type->id.value()
+ << std::dec;
+ }
+ }
+
+ for (auto& type : package->types) {
+ std::set<uint16_t> entryIds;
+ for (auto& entry : type->entries) {
+ if (!entry->id) {
+ return ::testing::AssertionFailure()
+ << "entry " << entry->name << " of type " << type->type
+ << " of package " << package->name << " has no ID";
+ }
+
+ if (!entryIds.insert(entry->id.value()).second) {
+ return ::testing::AssertionFailure()
+ << "entry " << entry->name << " of type " << type->type
+ << " of package " << package->name << " has non-unique ID "
+ << std::hex << (int)entry->id.value() << std::dec;
+ }
+ }
+ }
+ }
+ return ::testing::AssertionSuccess() << "all IDs are unique and assigned";
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/Image.h b/tools/aapt2/compile/Image.h
index fda6a3a..4cf2ea7 100644
--- a/tools/aapt2/compile/Image.h
+++ b/tools/aapt2/compile/Image.h
@@ -29,173 +29,180 @@
* An in-memory image, loaded from disk, with pixels in RGBA_8888 format.
*/
class Image {
-public:
- explicit Image() = default;
+ public:
+ explicit Image() = default;
- /**
- * A `height` sized array of pointers, where each element points to a
- * `width` sized row of RGBA_8888 pixels.
- */
- std::unique_ptr<uint8_t*[]> rows;
+ /**
+ * A `height` sized array of pointers, where each element points to a
+ * `width` sized row of RGBA_8888 pixels.
+ */
+ std::unique_ptr<uint8_t* []> rows;
- /**
- * The width of the image in RGBA_8888 pixels. This is int32_t because of 9-patch data
- * format limitations.
- */
- int32_t width = 0;
+ /**
+ * The width of the image in RGBA_8888 pixels. This is int32_t because of
+ * 9-patch data
+ * format limitations.
+ */
+ int32_t width = 0;
- /**
- * The height of the image in RGBA_8888 pixels. This is int32_t because of 9-patch data
- * format limitations.
- */
- int32_t height = 0;
+ /**
+ * The height of the image in RGBA_8888 pixels. This is int32_t because of
+ * 9-patch data
+ * format limitations.
+ */
+ int32_t height = 0;
- /**
- * Buffer to the raw image data stored sequentially.
- * Use `rows` to access the data on a row-by-row basis.
- */
- std::unique_ptr<uint8_t[]> data;
+ /**
+ * Buffer to the raw image data stored sequentially.
+ * Use `rows` to access the data on a row-by-row basis.
+ */
+ std::unique_ptr<uint8_t[]> data;
-private:
- DISALLOW_COPY_AND_ASSIGN(Image);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Image);
};
/**
- * A range of pixel values, starting at 'start' and ending before 'end' exclusive. Or rather [a, b).
+ * A range of pixel values, starting at 'start' and ending before 'end'
+ * exclusive. Or rather [a, b).
*/
struct Range {
- int32_t start = 0;
- int32_t end = 0;
+ int32_t start = 0;
+ int32_t end = 0;
- explicit Range() = default;
- inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {
- }
+ explicit Range() = default;
+ inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {}
};
inline bool operator==(const Range& left, const Range& right) {
- return left.start == right.start && left.end == right.end;
+ return left.start == right.start && left.end == right.end;
}
/**
- * Inset lengths from all edges of a rectangle. `left` and `top` are measured from the left and top
- * edges, while `right` and `bottom` are measured from the right and bottom edges, respectively.
+ * Inset lengths from all edges of a rectangle. `left` and `top` are measured
+ * from the left and top
+ * edges, while `right` and `bottom` are measured from the right and bottom
+ * edges, respectively.
*/
struct Bounds {
- int32_t left = 0;
- int32_t top = 0;
- int32_t right = 0;
- int32_t bottom = 0;
+ int32_t left = 0;
+ int32_t top = 0;
+ int32_t right = 0;
+ int32_t bottom = 0;
- explicit Bounds() = default;
- inline explicit Bounds(int32_t l, int32_t t, int32_t r, int32_t b) :
- left(l), top(t), right(r), bottom(b) {
- }
+ explicit Bounds() = default;
+ inline explicit Bounds(int32_t l, int32_t t, int32_t r, int32_t b)
+ : left(l), top(t), right(r), bottom(b) {}
- bool nonZero() const;
+ bool nonZero() const;
};
inline bool Bounds::nonZero() const {
- return left != 0 || top != 0 || right != 0 || bottom != 0;
+ return left != 0 || top != 0 || right != 0 || bottom != 0;
}
inline bool operator==(const Bounds& left, const Bounds& right) {
- return left.left == right.left && left.top == right.top &&
- left.right == right.right && left.bottom == right.bottom;
+ return left.left == right.left && left.top == right.top &&
+ left.right == right.right && left.bottom == right.bottom;
}
/**
- * Contains 9-patch data from a source image. All measurements exclude the 1px border of the
+ * Contains 9-patch data from a source image. All measurements exclude the 1px
+ * border of the
* source 9-patch image.
*/
class NinePatch {
-public:
- static std::unique_ptr<NinePatch> create(uint8_t** rows,
- const int32_t width, const int32_t height,
- std::string* errOut);
+ public:
+ static std::unique_ptr<NinePatch> create(uint8_t** rows, const int32_t width,
+ const int32_t height,
+ std::string* errOut);
- /**
- * Packs the RGBA_8888 data pointed to by pixel into a uint32_t
- * with format 0xAARRGGBB (the way 9-patch expects it).
- */
- static uint32_t packRGBA(const uint8_t* pixel);
+ /**
+ * Packs the RGBA_8888 data pointed to by pixel into a uint32_t
+ * with format 0xAARRGGBB (the way 9-patch expects it).
+ */
+ static uint32_t packRGBA(const uint8_t* pixel);
- /**
- * 9-patch content padding/insets. All positions are relative to the 9-patch
- * NOT including the 1px thick source border.
- */
- Bounds padding;
+ /**
+ * 9-patch content padding/insets. All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ */
+ Bounds padding;
- /**
- * Optical layout bounds/insets. This overrides the padding for
- * layout purposes. All positions are relative to the 9-patch
- * NOT including the 1px thick source border.
- * See https://developer.android.com/about/versions/android-4.3.html#OpticalBounds
- */
- Bounds layoutBounds;
+ /**
+ * Optical layout bounds/insets. This overrides the padding for
+ * layout purposes. All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ * See
+ * https://developer.android.com/about/versions/android-4.3.html#OpticalBounds
+ */
+ Bounds layoutBounds;
- /**
- * Outline of the image, calculated based on opacity.
- */
- Bounds outline;
+ /**
+ * Outline of the image, calculated based on opacity.
+ */
+ Bounds outline;
- /**
- * The computed radius of the outline. If non-zero, the outline is a rounded-rect.
- */
- float outlineRadius = 0.0f;
+ /**
+ * The computed radius of the outline. If non-zero, the outline is a
+ * rounded-rect.
+ */
+ float outlineRadius = 0.0f;
- /**
- * The largest alpha value within the outline.
- */
- uint32_t outlineAlpha = 0x000000ffu;
+ /**
+ * The largest alpha value within the outline.
+ */
+ uint32_t outlineAlpha = 0x000000ffu;
- /**
- * Horizontal regions of the image that are stretchable.
- * All positions are relative to the 9-patch
- * NOT including the 1px thick source border.
- */
- std::vector<Range> horizontalStretchRegions;
+ /**
+ * Horizontal regions of the image that are stretchable.
+ * All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ */
+ std::vector<Range> horizontalStretchRegions;
- /**
- * Vertical regions of the image that are stretchable.
- * All positions are relative to the 9-patch
- * NOT including the 1px thick source border.
- */
- std::vector<Range> verticalStretchRegions;
+ /**
+ * Vertical regions of the image that are stretchable.
+ * All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ */
+ std::vector<Range> verticalStretchRegions;
- /**
- * The colors within each region, fixed or stretchable.
- * For w*h regions, the color of region (x,y) is addressable
- * via index y*w + x.
- */
- std::vector<uint32_t> regionColors;
+ /**
+ * The colors within each region, fixed or stretchable.
+ * For w*h regions, the color of region (x,y) is addressable
+ * via index y*w + x.
+ */
+ std::vector<uint32_t> regionColors;
- /**
- * Returns serialized data containing the original basic 9-patch meta data.
- * Optical layout bounds and round rect outline data must be serialized
- * separately using serializeOpticalLayoutBounds() and serializeRoundedRectOutline().
- */
- std::unique_ptr<uint8_t[]> serializeBase(size_t* outLen) const;
+ /**
+ * Returns serialized data containing the original basic 9-patch meta data.
+ * Optical layout bounds and round rect outline data must be serialized
+ * separately using serializeOpticalLayoutBounds() and
+ * serializeRoundedRectOutline().
+ */
+ std::unique_ptr<uint8_t[]> serializeBase(size_t* outLen) const;
- /**
- * Serializes the layout bounds.
- */
- std::unique_ptr<uint8_t[]> serializeLayoutBounds(size_t* outLen) const;
+ /**
+ * Serializes the layout bounds.
+ */
+ std::unique_ptr<uint8_t[]> serializeLayoutBounds(size_t* outLen) const;
- /**
- * Serializes the rounded-rect outline.
- */
- std::unique_ptr<uint8_t[]> serializeRoundedRectOutline(size_t* outLen) const;
+ /**
+ * Serializes the rounded-rect outline.
+ */
+ std::unique_ptr<uint8_t[]> serializeRoundedRectOutline(size_t* outLen) const;
-private:
- explicit NinePatch() = default;
+ private:
+ explicit NinePatch() = default;
- DISALLOW_COPY_AND_ASSIGN(NinePatch);
+ DISALLOW_COPY_AND_ASSIGN(NinePatch);
};
::std::ostream& operator<<(::std::ostream& out, const Range& range);
::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds);
::std::ostream& operator<<(::std::ostream& out, const NinePatch& ninePatch);
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_COMPILE_IMAGE_H */
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp
index f965bff..56f72b5 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser.cpp
+++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp
@@ -14,9 +14,9 @@
* limitations under the License.
*/
+#include "compile/InlineXmlFormatParser.h"
#include "Debug.h"
#include "ResourceUtils.h"
-#include "compile/InlineXmlFormatParser.h"
#include "util/Util.h"
#include "xml/XmlDom.h"
#include "xml/XmlUtil.h"
@@ -33,158 +33,172 @@
* XML Visitor that will find all <aapt:attr> elements for extraction.
*/
class Visitor : public xml::PackageAwareVisitor {
-public:
- using xml::PackageAwareVisitor::visit;
+ public:
+ using xml::PackageAwareVisitor::visit;
- struct InlineDeclaration {
- xml::Element* el;
- std::string attrNamespaceUri;
- std::string attrName;
- };
+ struct InlineDeclaration {
+ xml::Element* el;
+ std::string attrNamespaceUri;
+ std::string attrName;
+ };
- explicit Visitor(IAaptContext* context, xml::XmlResource* xmlResource) :
- mContext(context), mXmlResource(xmlResource) {
+ explicit Visitor(IAaptContext* context, xml::XmlResource* xmlResource)
+ : mContext(context), mXmlResource(xmlResource) {}
+
+ void visit(xml::Element* el) override {
+ if (el->namespaceUri != xml::kSchemaAapt || el->name != "attr") {
+ xml::PackageAwareVisitor::visit(el);
+ return;
}
- void visit(xml::Element* el) override {
- if (el->namespaceUri != xml::kSchemaAapt || el->name != "attr") {
- xml::PackageAwareVisitor::visit(el);
- return;
- }
+ const Source& src = mXmlResource->file.source.withLine(el->lineNumber);
- const Source& src = mXmlResource->file.source.withLine(el->lineNumber);
-
- xml::Attribute* attr = el->findAttribute({}, "name");
- if (!attr) {
- mContext->getDiagnostics()->error(DiagMessage(src) << "missing 'name' attribute");
- mError = true;
- return;
- }
-
- Maybe<Reference> ref = ResourceUtils::parseXmlAttributeName(attr->value);
- if (!ref) {
- mContext->getDiagnostics()->error(DiagMessage(src) << "invalid XML attribute '"
- << attr->value << "'");
- mError = true;
- return;
- }
-
- const ResourceName& name = ref.value().name.value();
-
- // Use an empty string for the compilation package because we don't want to default to
- // the local package if the user specified name="style" or something. This should just
- // be the default namespace.
- Maybe<xml::ExtractedPackage> maybePkg = transformPackageAlias(name.package, {});
- if (!maybePkg) {
- mContext->getDiagnostics()->error(DiagMessage(src) << "invalid namespace prefix '"
- << name.package << "'");
- mError = true;
- return;
- }
-
- const xml::ExtractedPackage& pkg = maybePkg.value();
- const bool privateNamespace = pkg.privateNamespace || ref.value().privateReference;
-
- InlineDeclaration decl;
- decl.el = el;
- decl.attrName = name.entry;
- if (!pkg.package.empty()) {
- decl.attrNamespaceUri = xml::buildPackageNamespace(pkg.package, privateNamespace);
- }
-
- mInlineDeclarations.push_back(std::move(decl));
+ xml::Attribute* attr = el->findAttribute({}, "name");
+ if (!attr) {
+ mContext->getDiagnostics()->error(DiagMessage(src)
+ << "missing 'name' attribute");
+ mError = true;
+ return;
}
- const std::vector<InlineDeclaration>& getInlineDeclarations() const {
- return mInlineDeclarations;
+ Maybe<Reference> ref = ResourceUtils::parseXmlAttributeName(attr->value);
+ if (!ref) {
+ mContext->getDiagnostics()->error(
+ DiagMessage(src) << "invalid XML attribute '" << attr->value << "'");
+ mError = true;
+ return;
}
- bool hasError() const {
- return mError;
+ const ResourceName& name = ref.value().name.value();
+
+ // Use an empty string for the compilation package because we don't want to
+ // default to
+ // the local package if the user specified name="style" or something. This
+ // should just
+ // be the default namespace.
+ Maybe<xml::ExtractedPackage> maybePkg =
+ transformPackageAlias(name.package, {});
+ if (!maybePkg) {
+ mContext->getDiagnostics()->error(DiagMessage(src)
+ << "invalid namespace prefix '"
+ << name.package << "'");
+ mError = true;
+ return;
}
-private:
- DISALLOW_COPY_AND_ASSIGN(Visitor);
+ const xml::ExtractedPackage& pkg = maybePkg.value();
+ const bool privateNamespace =
+ pkg.privateNamespace || ref.value().privateReference;
- IAaptContext* mContext;
- xml::XmlResource* mXmlResource;
- std::vector<InlineDeclaration> mInlineDeclarations;
- bool mError = false;
+ InlineDeclaration decl;
+ decl.el = el;
+ decl.attrName = name.entry;
+ if (!pkg.package.empty()) {
+ decl.attrNamespaceUri =
+ xml::buildPackageNamespace(pkg.package, privateNamespace);
+ }
+
+ mInlineDeclarations.push_back(std::move(decl));
+ }
+
+ const std::vector<InlineDeclaration>& getInlineDeclarations() const {
+ return mInlineDeclarations;
+ }
+
+ bool hasError() const { return mError; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Visitor);
+
+ IAaptContext* mContext;
+ xml::XmlResource* mXmlResource;
+ std::vector<InlineDeclaration> mInlineDeclarations;
+ bool mError = false;
};
-} // namespace
+} // namespace
-bool InlineXmlFormatParser::consume(IAaptContext* context, xml::XmlResource* doc) {
- Visitor visitor(context, doc);
- doc->root->accept(&visitor);
- if (visitor.hasError()) {
+bool InlineXmlFormatParser::consume(IAaptContext* context,
+ xml::XmlResource* doc) {
+ Visitor visitor(context, doc);
+ doc->root->accept(&visitor);
+ if (visitor.hasError()) {
+ return false;
+ }
+
+ size_t nameSuffixCounter = 0;
+ for (const Visitor::InlineDeclaration& decl :
+ visitor.getInlineDeclarations()) {
+ auto newDoc = util::make_unique<xml::XmlResource>();
+ newDoc->file.config = doc->file.config;
+ newDoc->file.source = doc->file.source.withLine(decl.el->lineNumber);
+ newDoc->file.name = doc->file.name;
+
+ // Modify the new entry name. We need to suffix the entry with a number to
+ // avoid
+ // local collisions, then mangle it with the empty package, such that it
+ // won't show up
+ // in R.java.
+
+ newDoc->file.name.entry = NameMangler::mangleEntry(
+ {}, newDoc->file.name.entry + "__" + std::to_string(nameSuffixCounter));
+
+ // Extracted elements must be the only child of <aapt:attr>.
+ // Make sure there is one root node in the children (ignore empty text).
+ for (auto& child : decl.el->children) {
+ const Source childSource = doc->file.source.withLine(child->lineNumber);
+ if (xml::Text* t = xml::nodeCast<xml::Text>(child.get())) {
+ if (!util::trimWhitespace(t->text).empty()) {
+ context->getDiagnostics()->error(
+ DiagMessage(childSource)
+ << "can't extract text into its own resource");
+ return false;
+ }
+ } else if (newDoc->root) {
+ context->getDiagnostics()->error(
+ DiagMessage(childSource)
+ << "inline XML resources must have a single root");
return false;
+ } else {
+ newDoc->root = std::move(child);
+ newDoc->root->parent = nullptr;
+ }
}
- size_t nameSuffixCounter = 0;
- for (const Visitor::InlineDeclaration& decl : visitor.getInlineDeclarations()) {
- auto newDoc = util::make_unique<xml::XmlResource>();
- newDoc->file.config = doc->file.config;
- newDoc->file.source = doc->file.source.withLine(decl.el->lineNumber);
- newDoc->file.name = doc->file.name;
-
- // Modify the new entry name. We need to suffix the entry with a number to avoid
- // local collisions, then mangle it with the empty package, such that it won't show up
- // in R.java.
-
- newDoc->file.name.entry = NameMangler::mangleEntry(
- {}, newDoc->file.name.entry + "__" + std::to_string(nameSuffixCounter));
-
- // Extracted elements must be the only child of <aapt:attr>.
- // Make sure there is one root node in the children (ignore empty text).
- for (auto& child : decl.el->children) {
- const Source childSource = doc->file.source.withLine(child->lineNumber);
- if (xml::Text* t = xml::nodeCast<xml::Text>(child.get())) {
- if (!util::trimWhitespace(t->text).empty()) {
- context->getDiagnostics()->error(DiagMessage(childSource)
- << "can't extract text into its own resource");
- return false;
- }
- } else if (newDoc->root) {
- context->getDiagnostics()->error(DiagMessage(childSource)
- << "inline XML resources must have a single root");
- return false;
- } else {
- newDoc->root = std::move(child);
- newDoc->root->parent = nullptr;
- }
- }
-
- // Walk up and find the parent element.
- xml::Node* node = decl.el;
- xml::Element* parentEl = nullptr;
- while (node->parent && (parentEl = xml::nodeCast<xml::Element>(node->parent)) == nullptr) {
- node = node->parent;
- }
-
- if (!parentEl) {
- context->getDiagnostics()->error(DiagMessage(newDoc->file.source)
- << "no suitable parent for inheriting attribute");
- return false;
- }
-
- // Add the inline attribute to the parent.
- parentEl->attributes.push_back(xml::Attribute{
- decl.attrNamespaceUri, decl.attrName, "@" + newDoc->file.name.toString() });
-
- // Delete the subtree.
- for (auto iter = parentEl->children.begin(); iter != parentEl->children.end(); ++iter) {
- if (iter->get() == node) {
- parentEl->children.erase(iter);
- break;
- }
- }
-
- mQueue.push_back(std::move(newDoc));
-
- nameSuffixCounter++;
+ // Walk up and find the parent element.
+ xml::Node* node = decl.el;
+ xml::Element* parentEl = nullptr;
+ while (node->parent &&
+ (parentEl = xml::nodeCast<xml::Element>(node->parent)) == nullptr) {
+ node = node->parent;
}
- return true;
+
+ if (!parentEl) {
+ context->getDiagnostics()->error(
+ DiagMessage(newDoc->file.source)
+ << "no suitable parent for inheriting attribute");
+ return false;
+ }
+
+ // Add the inline attribute to the parent.
+ parentEl->attributes.push_back(
+ xml::Attribute{decl.attrNamespaceUri, decl.attrName,
+ "@" + newDoc->file.name.toString()});
+
+ // Delete the subtree.
+ for (auto iter = parentEl->children.begin();
+ iter != parentEl->children.end(); ++iter) {
+ if (iter->get() == node) {
+ parentEl->children.erase(iter);
+ break;
+ }
+ }
+
+ mQueue.push_back(std::move(newDoc));
+
+ nameSuffixCounter++;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.h b/tools/aapt2/compile/InlineXmlFormatParser.h
index 69065fd..cd8794b 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser.h
+++ b/tools/aapt2/compile/InlineXmlFormatParser.h
@@ -41,25 +41,28 @@
* </aapt:attr>
* </animated-vector>
*
- * The <vector> will be extracted into its own XML file and <animated-vector> will
- * gain an attribute 'android:drawable' set to a reference to the extracted <vector> resource.
+ * The <vector> will be extracted into its own XML file and <animated-vector>
+ * will
+ * gain an attribute 'android:drawable' set to a reference to the extracted
+ * <vector> resource.
*/
class InlineXmlFormatParser : public IXmlResourceConsumer {
-public:
- explicit InlineXmlFormatParser() = default;
+ public:
+ explicit InlineXmlFormatParser() = default;
- bool consume(IAaptContext* context, xml::XmlResource* doc) override;
+ bool consume(IAaptContext* context, xml::XmlResource* doc) override;
- std::vector<std::unique_ptr<xml::XmlResource>>& getExtractedInlineXmlDocuments() {
- return mQueue;
- }
+ std::vector<std::unique_ptr<xml::XmlResource>>&
+ getExtractedInlineXmlDocuments() {
+ return mQueue;
+ }
-private:
- DISALLOW_COPY_AND_ASSIGN(InlineXmlFormatParser);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(InlineXmlFormatParser);
- std::vector<std::unique_ptr<xml::XmlResource>> mQueue;
+ std::vector<std::unique_ptr<xml::XmlResource>> mQueue;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_COMPILE_INLINEXMLFORMATPARSER_H */
diff --git a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
index 8d62210..4adb21c 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
+++ b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
@@ -20,22 +20,22 @@
namespace aapt {
TEST(InlineXmlFormatParserTest, PassThrough) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android">
<View android:text="hey">
<View android:id="hi" />
</View>
</View>)EOF");
- InlineXmlFormatParser parser;
- ASSERT_TRUE(parser.consume(context.get(), doc.get()));
- EXPECT_EQ(0u, parser.getExtractedInlineXmlDocuments().size());
+ InlineXmlFormatParser parser;
+ ASSERT_TRUE(parser.consume(context.get(), doc.get()));
+ EXPECT_EQ(0u, parser.getExtractedInlineXmlDocuments().size());
}
TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View1 xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:text">
@@ -45,47 +45,48 @@
</aapt:attr>
</View1>)EOF");
- doc->file.name = test::parseNameOrDie("layout/main");
+ doc->file.name = test::parseNameOrDie("layout/main");
- InlineXmlFormatParser parser;
- ASSERT_TRUE(parser.consume(context.get(), doc.get()));
+ InlineXmlFormatParser parser;
+ ASSERT_TRUE(parser.consume(context.get(), doc.get()));
- // One XML resource should have been extracted.
- EXPECT_EQ(1u, parser.getExtractedInlineXmlDocuments().size());
+ // One XML resource should have been extracted.
+ EXPECT_EQ(1u, parser.getExtractedInlineXmlDocuments().size());
- xml::Element* el = xml::findRootElement(doc.get());
- ASSERT_NE(nullptr, el);
+ xml::Element* el = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
- EXPECT_EQ("View1", el->name);
+ EXPECT_EQ("View1", el->name);
- // The <aapt:attr> tag should be extracted.
- EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
+ // The <aapt:attr> tag should be extracted.
+ EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
- // The 'android:text' attribute should be set with a reference.
- xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "text");
- ASSERT_NE(nullptr, attr);
+ // The 'android:text' attribute should be set with a reference.
+ xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "text");
+ ASSERT_NE(nullptr, attr);
- ResourceNameRef nameRef;
- ASSERT_TRUE(ResourceUtils::parseReference(attr->value, &nameRef));
+ ResourceNameRef nameRef;
+ ASSERT_TRUE(ResourceUtils::parseReference(attr->value, &nameRef));
- xml::XmlResource* extractedDoc = parser.getExtractedInlineXmlDocuments()[0].get();
- ASSERT_NE(nullptr, extractedDoc);
+ xml::XmlResource* extractedDoc =
+ parser.getExtractedInlineXmlDocuments()[0].get();
+ ASSERT_NE(nullptr, extractedDoc);
- // Make sure the generated reference is correct.
- EXPECT_EQ(nameRef.package, extractedDoc->file.name.package);
- EXPECT_EQ(nameRef.type, extractedDoc->file.name.type);
- EXPECT_EQ(nameRef.entry, extractedDoc->file.name.entry);
+ // Make sure the generated reference is correct.
+ EXPECT_EQ(nameRef.package, extractedDoc->file.name.package);
+ EXPECT_EQ(nameRef.type, extractedDoc->file.name.type);
+ EXPECT_EQ(nameRef.entry, extractedDoc->file.name.entry);
- // Verify the structure of the extracted XML.
- el = xml::findRootElement(extractedDoc);
- ASSERT_NE(nullptr, el);
- EXPECT_EQ("View2", el->name);
- EXPECT_NE(nullptr, el->findChild({}, "View3"));
+ // Verify the structure of the extracted XML.
+ el = xml::findRootElement(extractedDoc);
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ("View2", el->name);
+ EXPECT_NE(nullptr, el->findChild({}, "View3"));
}
TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View1 xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:text">
@@ -99,40 +100,43 @@
</aapt:attr>
</View1>)EOF");
- doc->file.name = test::parseNameOrDie("layout/main");
+ doc->file.name = test::parseNameOrDie("layout/main");
- InlineXmlFormatParser parser;
- ASSERT_TRUE(parser.consume(context.get(), doc.get()));
- ASSERT_EQ(2u, parser.getExtractedInlineXmlDocuments().size());
+ InlineXmlFormatParser parser;
+ ASSERT_TRUE(parser.consume(context.get(), doc.get()));
+ ASSERT_EQ(2u, parser.getExtractedInlineXmlDocuments().size());
- xml::Element* el = xml::findRootElement(doc.get());
- ASSERT_NE(nullptr, el);
+ xml::Element* el = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
- EXPECT_EQ("View1", el->name);
+ EXPECT_EQ("View1", el->name);
- xml::Attribute* attrText = el->findAttribute(xml::kSchemaAndroid, "text");
- ASSERT_NE(nullptr, attrText);
+ xml::Attribute* attrText = el->findAttribute(xml::kSchemaAndroid, "text");
+ ASSERT_NE(nullptr, attrText);
- xml::Attribute* attrDrawable = el->findAttribute(xml::kSchemaAndroid, "drawable");
- ASSERT_NE(nullptr, attrDrawable);
+ xml::Attribute* attrDrawable =
+ el->findAttribute(xml::kSchemaAndroid, "drawable");
+ ASSERT_NE(nullptr, attrDrawable);
- // The two extracted resources should have different names.
- EXPECT_NE(attrText->value, attrDrawable->value);
+ // The two extracted resources should have different names.
+ EXPECT_NE(attrText->value, attrDrawable->value);
- // The child <aapt:attr> elements should be gone.
- EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
+ // The child <aapt:attr> elements should be gone.
+ EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
- xml::XmlResource* extractedDocText = parser.getExtractedInlineXmlDocuments()[0].get();
- ASSERT_NE(nullptr, extractedDocText);
- el = xml::findRootElement(extractedDocText);
- ASSERT_NE(nullptr, el);
- EXPECT_EQ("View2", el->name);
+ xml::XmlResource* extractedDocText =
+ parser.getExtractedInlineXmlDocuments()[0].get();
+ ASSERT_NE(nullptr, extractedDocText);
+ el = xml::findRootElement(extractedDocText);
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ("View2", el->name);
- xml::XmlResource* extractedDocDrawable = parser.getExtractedInlineXmlDocuments()[1].get();
- ASSERT_NE(nullptr, extractedDocDrawable);
- el = xml::findRootElement(extractedDocDrawable);
- ASSERT_NE(nullptr, el);
- EXPECT_EQ("vector", el->name);
+ xml::XmlResource* extractedDocDrawable =
+ parser.getExtractedInlineXmlDocuments()[1].get();
+ ASSERT_NE(nullptr, extractedDocDrawable);
+ el = xml::findRootElement(extractedDocDrawable);
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ("vector", el->name);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/NinePatch.cpp b/tools/aapt2/compile/NinePatch.cpp
index 0fc1c5d..8842eb7 100644
--- a/tools/aapt2/compile/NinePatch.cpp
+++ b/tools/aapt2/compile/NinePatch.cpp
@@ -28,7 +28,7 @@
// Colors in the format 0xAARRGGBB (the way 9-patch expects it).
constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu;
constexpr static const uint32_t kColorOpaqueBlack = 0xff000000u;
-constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u;
+constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u;
constexpr static const uint32_t kPrimaryColor = kColorOpaqueBlack;
constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed;
@@ -46,35 +46,37 @@
* but we need to ensure consistency throughout the image.
*/
class ColorValidator {
-public:
- virtual ~ColorValidator() = default;
+ public:
+ virtual ~ColorValidator() = default;
- /**
- * Returns true if the color specified is a neutral color
- * (no padding, stretching, or optical bounds).
- */
- virtual bool isNeutralColor(uint32_t color) const = 0;
+ /**
+ * Returns true if the color specified is a neutral color
+ * (no padding, stretching, or optical bounds).
+ */
+ virtual bool isNeutralColor(uint32_t color) const = 0;
- /**
- * Returns true if the color is either a neutral color
- * or one denoting padding, stretching, or optical bounds.
- */
- bool isValidColor(uint32_t color) const {
- switch (color) {
- case kPrimaryColor:
- case kSecondaryColor:
- return true;
- }
- return isNeutralColor(color);
+ /**
+ * Returns true if the color is either a neutral color
+ * or one denoting padding, stretching, or optical bounds.
+ */
+ bool isValidColor(uint32_t color) const {
+ switch (color) {
+ case kPrimaryColor:
+ case kSecondaryColor:
+ return true;
}
+ return isNeutralColor(color);
+ }
};
// Walks an ImageLine and records Ranges of primary and secondary colors.
-// The primary color is black and is used to denote a padding or stretching range,
+// The primary color is black and is used to denote a padding or stretching
+// range,
// depending on which border we're iterating over.
// The secondary color is red and is used to denote optical bounds.
//
-// An ImageLine is a templated-interface that would look something like this if it
+// An ImageLine is a templated-interface that would look something like this if
+// it
// were polymorphic:
//
// class ImageLine {
@@ -87,590 +89,604 @@
static bool fillRanges(const ImageLine* imageLine,
const ColorValidator* colorValidator,
std::vector<Range>* primaryRanges,
- std::vector<Range>* secondaryRanges,
- std::string* err) {
- const int32_t length = imageLine->getLength();
+ std::vector<Range>* secondaryRanges, std::string* err) {
+ const int32_t length = imageLine->getLength();
- uint32_t lastColor = 0xffffffffu;
- for (int32_t idx = 1; idx < length - 1; idx++) {
- const uint32_t color = imageLine->getColor(idx);
- if (!colorValidator->isValidColor(color)) {
- *err = "found an invalid color";
- return false;
- }
-
- if (color != lastColor) {
- // We are ending a range. Which range?
- // note: encode the x offset without the final 1 pixel border.
- if (lastColor == kPrimaryColor) {
- primaryRanges->back().end = idx - 1;
- } else if (lastColor == kSecondaryColor) {
- secondaryRanges->back().end = idx - 1;
- }
-
- // We are starting a range. Which range?
- // note: encode the x offset without the final 1 pixel border.
- if (color == kPrimaryColor) {
- primaryRanges->push_back(Range(idx - 1, length - 2));
- } else if (color == kSecondaryColor) {
- secondaryRanges->push_back(Range(idx - 1, length - 2));
- }
- lastColor = color;
- }
+ uint32_t lastColor = 0xffffffffu;
+ for (int32_t idx = 1; idx < length - 1; idx++) {
+ const uint32_t color = imageLine->getColor(idx);
+ if (!colorValidator->isValidColor(color)) {
+ *err = "found an invalid color";
+ return false;
}
- return true;
+
+ if (color != lastColor) {
+ // We are ending a range. Which range?
+ // note: encode the x offset without the final 1 pixel border.
+ if (lastColor == kPrimaryColor) {
+ primaryRanges->back().end = idx - 1;
+ } else if (lastColor == kSecondaryColor) {
+ secondaryRanges->back().end = idx - 1;
+ }
+
+ // We are starting a range. Which range?
+ // note: encode the x offset without the final 1 pixel border.
+ if (color == kPrimaryColor) {
+ primaryRanges->push_back(Range(idx - 1, length - 2));
+ } else if (color == kSecondaryColor) {
+ secondaryRanges->push_back(Range(idx - 1, length - 2));
+ }
+ lastColor = color;
+ }
+ }
+ return true;
}
/**
- * Iterates over a row in an image. Implements the templated ImageLine interface.
+ * Iterates over a row in an image. Implements the templated ImageLine
+ * interface.
*/
class HorizontalImageLine {
-public:
- explicit HorizontalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
- int32_t length) :
- mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) {
- }
+ public:
+ explicit HorizontalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
+ int32_t length)
+ : mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) {}
- inline int32_t getLength() const {
- return mLength;
- }
+ inline int32_t getLength() const { return mLength; }
- inline uint32_t getColor(int32_t idx) const {
- return NinePatch::packRGBA(mRows[mYOffset] + (idx + mXOffset) * 4);
- }
+ inline uint32_t getColor(int32_t idx) const {
+ return NinePatch::packRGBA(mRows[mYOffset] + (idx + mXOffset) * 4);
+ }
-private:
- uint8_t** mRows;
- int32_t mXOffset, mYOffset, mLength;
+ private:
+ uint8_t** mRows;
+ int32_t mXOffset, mYOffset, mLength;
- DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine);
+ DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine);
};
/**
- * Iterates over a column in an image. Implements the templated ImageLine interface.
+ * Iterates over a column in an image. Implements the templated ImageLine
+ * interface.
*/
class VerticalImageLine {
-public:
- explicit VerticalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
- int32_t length) :
- mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) {
- }
+ public:
+ explicit VerticalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
+ int32_t length)
+ : mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) {}
- inline int32_t getLength() const {
- return mLength;
- }
+ inline int32_t getLength() const { return mLength; }
- inline uint32_t getColor(int32_t idx) const {
- return NinePatch::packRGBA(mRows[mYOffset + idx] + (mXOffset * 4));
- }
+ inline uint32_t getColor(int32_t idx) const {
+ return NinePatch::packRGBA(mRows[mYOffset + idx] + (mXOffset * 4));
+ }
-private:
- uint8_t** mRows;
- int32_t mXOffset, mYOffset, mLength;
+ private:
+ uint8_t** mRows;
+ int32_t mXOffset, mYOffset, mLength;
- DISALLOW_COPY_AND_ASSIGN(VerticalImageLine);
+ DISALLOW_COPY_AND_ASSIGN(VerticalImageLine);
};
class DiagonalImageLine {
-public:
- explicit DiagonalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
- int32_t xStep, int32_t yStep, int32_t length) :
- mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mXStep(xStep), mYStep(yStep),
- mLength(length) {
- }
+ public:
+ explicit DiagonalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
+ int32_t xStep, int32_t yStep, int32_t length)
+ : mRows(rows),
+ mXOffset(xOffset),
+ mYOffset(yOffset),
+ mXStep(xStep),
+ mYStep(yStep),
+ mLength(length) {}
- inline int32_t getLength() const {
- return mLength;
- }
+ inline int32_t getLength() const { return mLength; }
- inline uint32_t getColor(int32_t idx) const {
- return NinePatch::packRGBA(
- mRows[mYOffset + (idx * mYStep)] + ((idx + mXOffset) * mXStep) * 4);
- }
+ inline uint32_t getColor(int32_t idx) const {
+ return NinePatch::packRGBA(mRows[mYOffset + (idx * mYStep)] +
+ ((idx + mXOffset) * mXStep) * 4);
+ }
-private:
- uint8_t** mRows;
- int32_t mXOffset, mYOffset, mXStep, mYStep, mLength;
+ private:
+ uint8_t** mRows;
+ int32_t mXOffset, mYOffset, mXStep, mYStep, mLength;
- DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine);
+ DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine);
};
class TransparentNeutralColorValidator : public ColorValidator {
-public:
- bool isNeutralColor(uint32_t color) const override {
- return getAlpha(color) == 0;
- }
+ public:
+ bool isNeutralColor(uint32_t color) const override {
+ return getAlpha(color) == 0;
+ }
};
class WhiteNeutralColorValidator : public ColorValidator {
-public:
- bool isNeutralColor(uint32_t color) const override {
- return color == kColorOpaqueWhite;
- }
+ public:
+ bool isNeutralColor(uint32_t color) const override {
+ return color == kColorOpaqueWhite;
+ }
};
inline static uint32_t getAlpha(uint32_t color) {
- return (color & 0xff000000u) >> 24;
+ return (color & 0xff000000u) >> 24;
}
static bool populateBounds(const std::vector<Range>& padding,
const std::vector<Range>& layoutBounds,
const std::vector<Range>& stretchRegions,
- const int32_t length,
- int32_t* paddingStart, int32_t* paddingEnd,
- int32_t* layoutStart, int32_t* layoutEnd,
- const StringPiece& edgeName,
+ const int32_t length, int32_t* paddingStart,
+ int32_t* paddingEnd, int32_t* layoutStart,
+ int32_t* layoutEnd, const StringPiece& edgeName,
std::string* err) {
- if (padding.size() > 1) {
+ if (padding.size() > 1) {
+ std::stringstream errStream;
+ errStream << "too many padding sections on " << edgeName << " border";
+ *err = errStream.str();
+ return false;
+ }
+
+ *paddingStart = 0;
+ *paddingEnd = 0;
+ if (!padding.empty()) {
+ const Range& range = padding.front();
+ *paddingStart = range.start;
+ *paddingEnd = length - range.end;
+ } else if (!stretchRegions.empty()) {
+ // No padding was defined. Compute the padding from the first and last
+ // stretch regions.
+ *paddingStart = stretchRegions.front().start;
+ *paddingEnd = length - stretchRegions.back().end;
+ }
+
+ if (layoutBounds.size() > 2) {
+ std::stringstream errStream;
+ errStream << "too many layout bounds sections on " << edgeName << " border";
+ *err = errStream.str();
+ return false;
+ }
+
+ *layoutStart = 0;
+ *layoutEnd = 0;
+ if (layoutBounds.size() >= 1) {
+ const Range& range = layoutBounds.front();
+ // If there is only one layout bound segment, it might not start at 0, but
+ // then it should
+ // end at length.
+ if (range.start != 0 && range.end != length) {
+ std::stringstream errStream;
+ errStream << "layout bounds on " << edgeName
+ << " border must start at edge";
+ *err = errStream.str();
+ return false;
+ }
+ *layoutStart = range.end;
+
+ if (layoutBounds.size() >= 2) {
+ const Range& range = layoutBounds.back();
+ if (range.end != length) {
std::stringstream errStream;
- errStream << "too many padding sections on " << edgeName << " border";
+ errStream << "layout bounds on " << edgeName
+ << " border must start at edge";
*err = errStream.str();
return false;
+ }
+ *layoutEnd = length - range.start;
}
-
- *paddingStart = 0;
- *paddingEnd = 0;
- if (!padding.empty()) {
- const Range& range = padding.front();
- *paddingStart = range.start;
- *paddingEnd = length - range.end;
- } else if (!stretchRegions.empty()) {
- // No padding was defined. Compute the padding from the first and last
- // stretch regions.
- *paddingStart = stretchRegions.front().start;
- *paddingEnd = length - stretchRegions.back().end;
- }
-
- if (layoutBounds.size() > 2) {
- std::stringstream errStream;
- errStream << "too many layout bounds sections on " << edgeName << " border";
- *err = errStream.str();
- return false;
- }
-
- *layoutStart = 0;
- *layoutEnd = 0;
- if (layoutBounds.size() >= 1) {
- const Range& range = layoutBounds.front();
- // If there is only one layout bound segment, it might not start at 0, but then it should
- // end at length.
- if (range.start != 0 && range.end != length) {
- std::stringstream errStream;
- errStream << "layout bounds on " << edgeName << " border must start at edge";
- *err = errStream.str();
- return false;
- }
- *layoutStart = range.end;
-
- if (layoutBounds.size() >= 2) {
- const Range& range = layoutBounds.back();
- if (range.end != length) {
- std::stringstream errStream;
- errStream << "layout bounds on " << edgeName << " border must start at edge";
- *err = errStream.str();
- return false;
- }
- *layoutEnd = length - range.start;
- }
- }
- return true;
+ }
+ return true;
}
-static int32_t calculateSegmentCount(const std::vector<Range>& stretchRegions, int32_t length) {
- if (stretchRegions.size() == 0) {
- return 0;
- }
+static int32_t calculateSegmentCount(const std::vector<Range>& stretchRegions,
+ int32_t length) {
+ if (stretchRegions.size() == 0) {
+ return 0;
+ }
- const bool startIsFixed = stretchRegions.front().start != 0;
- const bool endIsFixed = stretchRegions.back().end != length;
- int32_t modifier = 0;
- if (startIsFixed && endIsFixed) {
- modifier = 1;
- } else if (!startIsFixed && !endIsFixed) {
- modifier = -1;
- }
- return static_cast<int32_t>(stretchRegions.size()) * 2 + modifier;
+ const bool startIsFixed = stretchRegions.front().start != 0;
+ const bool endIsFixed = stretchRegions.back().end != length;
+ int32_t modifier = 0;
+ if (startIsFixed && endIsFixed) {
+ modifier = 1;
+ } else if (!startIsFixed && !endIsFixed) {
+ modifier = -1;
+ }
+ return static_cast<int32_t>(stretchRegions.size()) * 2 + modifier;
}
static uint32_t getRegionColor(uint8_t** rows, const Bounds& region) {
- // Sample the first pixel to compare against.
- const uint32_t expectedColor = NinePatch::packRGBA(rows[region.top] + region.left * 4);
- for (int32_t y = region.top; y < region.bottom; y++) {
- const uint8_t* row = rows[y];
- for (int32_t x = region.left; x < region.right; x++) {
- const uint32_t color = NinePatch::packRGBA(row + x * 4);
- if (getAlpha(color) == 0) {
- // The color is transparent.
- // If the expectedColor is not transparent, NO_COLOR.
- if (getAlpha(expectedColor) != 0) {
- return android::Res_png_9patch::NO_COLOR;
- }
- } else if (color != expectedColor) {
- return android::Res_png_9patch::NO_COLOR;
- }
+ // Sample the first pixel to compare against.
+ const uint32_t expectedColor =
+ NinePatch::packRGBA(rows[region.top] + region.left * 4);
+ for (int32_t y = region.top; y < region.bottom; y++) {
+ const uint8_t* row = rows[y];
+ for (int32_t x = region.left; x < region.right; x++) {
+ const uint32_t color = NinePatch::packRGBA(row + x * 4);
+ if (getAlpha(color) == 0) {
+ // The color is transparent.
+ // If the expectedColor is not transparent, NO_COLOR.
+ if (getAlpha(expectedColor) != 0) {
+ return android::Res_png_9patch::NO_COLOR;
}
+ } else if (color != expectedColor) {
+ return android::Res_png_9patch::NO_COLOR;
+ }
}
+ }
- if (getAlpha(expectedColor) == 0) {
- return android::Res_png_9patch::TRANSPARENT_COLOR;
- }
- return expectedColor;
+ if (getAlpha(expectedColor) == 0) {
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
+ }
+ return expectedColor;
}
-// Fills outColors with each 9-patch section's colour. If the whole section is transparent,
-// it gets the special TRANSPARENT colour. If the whole section is the same colour, it is assigned
+// Fills outColors with each 9-patch section's colour. If the whole section is
+// transparent,
+// it gets the special TRANSPARENT colour. If the whole section is the same
+// colour, it is assigned
// that colour. Otherwise it gets the special NO_COLOR colour.
//
-// Note that the rows contain the 9-patch 1px border, and the indices in the stretch regions are
-// already offset to exclude the border. This means that each time the rows are accessed,
+// Note that the rows contain the 9-patch 1px border, and the indices in the
+// stretch regions are
+// already offset to exclude the border. This means that each time the rows are
+// accessed,
// the indices must be offset by 1.
//
// width and height also include the 9-patch 1px border.
-static void calculateRegionColors(uint8_t** rows,
- const std::vector<Range>& horizontalStretchRegions,
- const std::vector<Range>& verticalStretchRegions,
- const int32_t width, const int32_t height,
- std::vector<uint32_t>* outColors) {
- int32_t nextTop = 0;
- Bounds bounds;
- auto rowIter = verticalStretchRegions.begin();
- while (nextTop != height) {
- if (rowIter != verticalStretchRegions.end()) {
- if (nextTop != rowIter->start) {
- // This is a fixed segment.
- // Offset the bounds by 1 to accommodate the border.
- bounds.top = nextTop + 1;
- bounds.bottom = rowIter->start + 1;
- nextTop = rowIter->start;
- } else {
- // This is a stretchy segment.
- // Offset the bounds by 1 to accommodate the border.
- bounds.top = rowIter->start + 1;
- bounds.bottom = rowIter->end + 1;
- nextTop = rowIter->end;
- ++rowIter;
- }
- } else {
- // This is the end, fixed section.
- // Offset the bounds by 1 to accommodate the border.
- bounds.top = nextTop + 1;
- bounds.bottom = height + 1;
- nextTop = height;
- }
-
- int32_t nextLeft = 0;
- auto colIter = horizontalStretchRegions.begin();
- while (nextLeft != width) {
- if (colIter != horizontalStretchRegions.end()) {
- if (nextLeft != colIter->start) {
- // This is a fixed segment.
- // Offset the bounds by 1 to accommodate the border.
- bounds.left = nextLeft + 1;
- bounds.right = colIter->start + 1;
- nextLeft = colIter->start;
- } else {
- // This is a stretchy segment.
- // Offset the bounds by 1 to accommodate the border.
- bounds.left = colIter->start + 1;
- bounds.right = colIter->end + 1;
- nextLeft = colIter->end;
- ++colIter;
- }
- } else {
- // This is the end, fixed section.
- // Offset the bounds by 1 to accommodate the border.
- bounds.left = nextLeft + 1;
- bounds.right = width + 1;
- nextLeft = width;
- }
- outColors->push_back(getRegionColor(rows, bounds));
- }
+static void calculateRegionColors(
+ uint8_t** rows, const std::vector<Range>& horizontalStretchRegions,
+ const std::vector<Range>& verticalStretchRegions, const int32_t width,
+ const int32_t height, std::vector<uint32_t>* outColors) {
+ int32_t nextTop = 0;
+ Bounds bounds;
+ auto rowIter = verticalStretchRegions.begin();
+ while (nextTop != height) {
+ if (rowIter != verticalStretchRegions.end()) {
+ if (nextTop != rowIter->start) {
+ // This is a fixed segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.top = nextTop + 1;
+ bounds.bottom = rowIter->start + 1;
+ nextTop = rowIter->start;
+ } else {
+ // This is a stretchy segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.top = rowIter->start + 1;
+ bounds.bottom = rowIter->end + 1;
+ nextTop = rowIter->end;
+ ++rowIter;
+ }
+ } else {
+ // This is the end, fixed section.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.top = nextTop + 1;
+ bounds.bottom = height + 1;
+ nextTop = height;
}
+
+ int32_t nextLeft = 0;
+ auto colIter = horizontalStretchRegions.begin();
+ while (nextLeft != width) {
+ if (colIter != horizontalStretchRegions.end()) {
+ if (nextLeft != colIter->start) {
+ // This is a fixed segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.left = nextLeft + 1;
+ bounds.right = colIter->start + 1;
+ nextLeft = colIter->start;
+ } else {
+ // This is a stretchy segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.left = colIter->start + 1;
+ bounds.right = colIter->end + 1;
+ nextLeft = colIter->end;
+ ++colIter;
+ }
+ } else {
+ // This is the end, fixed section.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.left = nextLeft + 1;
+ bounds.right = width + 1;
+ nextLeft = width;
+ }
+ outColors->push_back(getRegionColor(rows, bounds));
+ }
+ }
}
-// Calculates the insets of a row/column of pixels based on where the largest alpha value begins
+// Calculates the insets of a row/column of pixels based on where the largest
+// alpha value begins
// (on both sides).
template <typename ImageLine>
-static void findOutlineInsets(const ImageLine* imageLine, int32_t* outStart, int32_t* outEnd) {
- *outStart = 0;
- *outEnd = 0;
+static void findOutlineInsets(const ImageLine* imageLine, int32_t* outStart,
+ int32_t* outEnd) {
+ *outStart = 0;
+ *outEnd = 0;
- const int32_t length = imageLine->getLength();
- if (length < 3) {
- return;
- }
-
- // If the length is odd, we want both sides to process the center pixel,
- // so we use two different midpoints (to account for < and <= in the different loops).
- const int32_t mid2 = length / 2;
- const int32_t mid1 = mid2 + (length % 2);
-
- uint32_t maxAlpha = 0;
- for (int32_t i = 0; i < mid1 && maxAlpha != 0xff; i++) {
- uint32_t alpha = getAlpha(imageLine->getColor(i));
- if (alpha > maxAlpha) {
- maxAlpha = alpha;
- *outStart = i;
- }
- }
-
- maxAlpha = 0;
- for (int32_t i = length - 1; i >= mid2 && maxAlpha != 0xff; i--) {
- uint32_t alpha = getAlpha(imageLine->getColor(i));
- if (alpha > maxAlpha) {
- maxAlpha = alpha;
- *outEnd = length - (i + 1);
- }
- }
+ const int32_t length = imageLine->getLength();
+ if (length < 3) {
return;
+ }
+
+ // If the length is odd, we want both sides to process the center pixel,
+ // so we use two different midpoints (to account for < and <= in the different
+ // loops).
+ const int32_t mid2 = length / 2;
+ const int32_t mid1 = mid2 + (length % 2);
+
+ uint32_t maxAlpha = 0;
+ for (int32_t i = 0; i < mid1 && maxAlpha != 0xff; i++) {
+ uint32_t alpha = getAlpha(imageLine->getColor(i));
+ if (alpha > maxAlpha) {
+ maxAlpha = alpha;
+ *outStart = i;
+ }
+ }
+
+ maxAlpha = 0;
+ for (int32_t i = length - 1; i >= mid2 && maxAlpha != 0xff; i--) {
+ uint32_t alpha = getAlpha(imageLine->getColor(i));
+ if (alpha > maxAlpha) {
+ maxAlpha = alpha;
+ *outEnd = length - (i + 1);
+ }
+ }
+ return;
}
template <typename ImageLine>
static uint32_t findMaxAlpha(const ImageLine* imageLine) {
- const int32_t length = imageLine->getLength();
- uint32_t maxAlpha = 0;
- for (int32_t idx = 0; idx < length && maxAlpha != 0xff; idx++) {
- uint32_t alpha = getAlpha(imageLine->getColor(idx));
- if (alpha > maxAlpha) {
- maxAlpha = alpha;
- }
+ const int32_t length = imageLine->getLength();
+ uint32_t maxAlpha = 0;
+ for (int32_t idx = 0; idx < length && maxAlpha != 0xff; idx++) {
+ uint32_t alpha = getAlpha(imageLine->getColor(idx));
+ if (alpha > maxAlpha) {
+ maxAlpha = alpha;
}
- return maxAlpha;
+ }
+ return maxAlpha;
}
// Pack the pixels in as 0xAARRGGBB (as 9-patch expects it).
uint32_t NinePatch::packRGBA(const uint8_t* pixel) {
- return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
+ return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
}
std::unique_ptr<NinePatch> NinePatch::create(uint8_t** rows,
- const int32_t width, const int32_t height,
+ const int32_t width,
+ const int32_t height,
std::string* err) {
- if (width < 3 || height < 3) {
- *err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
- return {};
- }
+ if (width < 3 || height < 3) {
+ *err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
+ return {};
+ }
- std::vector<Range> horizontalPadding;
- std::vector<Range> horizontalOpticalBounds;
- std::vector<Range> verticalPadding;
- std::vector<Range> verticalOpticalBounds;
- std::vector<Range> unexpectedRanges;
- std::unique_ptr<ColorValidator> colorValidator;
+ std::vector<Range> horizontalPadding;
+ std::vector<Range> horizontalOpticalBounds;
+ std::vector<Range> verticalPadding;
+ std::vector<Range> verticalOpticalBounds;
+ std::vector<Range> unexpectedRanges;
+ std::unique_ptr<ColorValidator> colorValidator;
- if (rows[0][3] == 0) {
- colorValidator = util::make_unique<TransparentNeutralColorValidator>();
- } else if (packRGBA(rows[0]) == kColorOpaqueWhite) {
- colorValidator = util::make_unique<WhiteNeutralColorValidator>();
- } else {
- *err = "top-left corner pixel must be either opaque white or transparent";
- return {};
- }
+ if (rows[0][3] == 0) {
+ colorValidator = util::make_unique<TransparentNeutralColorValidator>();
+ } else if (packRGBA(rows[0]) == kColorOpaqueWhite) {
+ colorValidator = util::make_unique<WhiteNeutralColorValidator>();
+ } else {
+ *err = "top-left corner pixel must be either opaque white or transparent";
+ return {};
+ }
- // Private constructor, can't use make_unique.
- auto ninePatch = std::unique_ptr<NinePatch>(new NinePatch());
+ // Private constructor, can't use make_unique.
+ auto ninePatch = std::unique_ptr<NinePatch>(new NinePatch());
- HorizontalImageLine topRow(rows, 0, 0, width);
- if (!fillRanges(&topRow, colorValidator.get(), &ninePatch->horizontalStretchRegions,
- &unexpectedRanges, err)) {
- return {};
- }
+ HorizontalImageLine topRow(rows, 0, 0, width);
+ if (!fillRanges(&topRow, colorValidator.get(),
+ &ninePatch->horizontalStretchRegions, &unexpectedRanges,
+ err)) {
+ return {};
+ }
- if (!unexpectedRanges.empty()) {
- const Range& range = unexpectedRanges[0];
- std::stringstream errStream;
- errStream << "found unexpected optical bounds (red pixel) on top border "
- << "at x=" << range.start + 1;
- *err = errStream.str();
- return {};
- }
+ if (!unexpectedRanges.empty()) {
+ const Range& range = unexpectedRanges[0];
+ std::stringstream errStream;
+ errStream << "found unexpected optical bounds (red pixel) on top border "
+ << "at x=" << range.start + 1;
+ *err = errStream.str();
+ return {};
+ }
- VerticalImageLine leftCol(rows, 0, 0, height);
- if (!fillRanges(&leftCol, colorValidator.get(), &ninePatch->verticalStretchRegions,
- &unexpectedRanges, err)) {
- return {};
- }
+ VerticalImageLine leftCol(rows, 0, 0, height);
+ if (!fillRanges(&leftCol, colorValidator.get(),
+ &ninePatch->verticalStretchRegions, &unexpectedRanges, err)) {
+ return {};
+ }
- if (!unexpectedRanges.empty()) {
- const Range& range = unexpectedRanges[0];
- std::stringstream errStream;
- errStream << "found unexpected optical bounds (red pixel) on left border "
- << "at y=" << range.start + 1;
- return {};
- }
+ if (!unexpectedRanges.empty()) {
+ const Range& range = unexpectedRanges[0];
+ std::stringstream errStream;
+ errStream << "found unexpected optical bounds (red pixel) on left border "
+ << "at y=" << range.start + 1;
+ return {};
+ }
- HorizontalImageLine bottomRow(rows, 0, height - 1, width);
- if (!fillRanges(&bottomRow, colorValidator.get(), &horizontalPadding,
- &horizontalOpticalBounds, err)) {
- return {};
- }
+ HorizontalImageLine bottomRow(rows, 0, height - 1, width);
+ if (!fillRanges(&bottomRow, colorValidator.get(), &horizontalPadding,
+ &horizontalOpticalBounds, err)) {
+ return {};
+ }
- if (!populateBounds(horizontalPadding, horizontalOpticalBounds,
- ninePatch->horizontalStretchRegions, width - 2,
- &ninePatch->padding.left, &ninePatch->padding.right,
- &ninePatch->layoutBounds.left, &ninePatch->layoutBounds.right,
- "bottom", err)) {
- return {};
- }
+ if (!populateBounds(horizontalPadding, horizontalOpticalBounds,
+ ninePatch->horizontalStretchRegions, width - 2,
+ &ninePatch->padding.left, &ninePatch->padding.right,
+ &ninePatch->layoutBounds.left,
+ &ninePatch->layoutBounds.right, "bottom", err)) {
+ return {};
+ }
- VerticalImageLine rightCol(rows, width - 1, 0, height);
- if (!fillRanges(&rightCol, colorValidator.get(), &verticalPadding,
- &verticalOpticalBounds, err)) {
- return {};
- }
+ VerticalImageLine rightCol(rows, width - 1, 0, height);
+ if (!fillRanges(&rightCol, colorValidator.get(), &verticalPadding,
+ &verticalOpticalBounds, err)) {
+ return {};
+ }
- if (!populateBounds(verticalPadding, verticalOpticalBounds,
- ninePatch->verticalStretchRegions, height - 2,
- &ninePatch->padding.top, &ninePatch->padding.bottom,
- &ninePatch->layoutBounds.top, &ninePatch->layoutBounds.bottom,
- "right", err)) {
- return {};
- }
+ if (!populateBounds(verticalPadding, verticalOpticalBounds,
+ ninePatch->verticalStretchRegions, height - 2,
+ &ninePatch->padding.top, &ninePatch->padding.bottom,
+ &ninePatch->layoutBounds.top,
+ &ninePatch->layoutBounds.bottom, "right", err)) {
+ return {};
+ }
- // Fill the region colors of the 9-patch.
- const int32_t numRows = calculateSegmentCount(ninePatch->horizontalStretchRegions, width - 2);
- const int32_t numCols = calculateSegmentCount(ninePatch->verticalStretchRegions, height - 2);
- if ((int64_t) numRows * (int64_t) numCols > 0x7f) {
- *err = "too many regions in 9-patch";
- return {};
- }
+ // Fill the region colors of the 9-patch.
+ const int32_t numRows =
+ calculateSegmentCount(ninePatch->horizontalStretchRegions, width - 2);
+ const int32_t numCols =
+ calculateSegmentCount(ninePatch->verticalStretchRegions, height - 2);
+ if ((int64_t)numRows * (int64_t)numCols > 0x7f) {
+ *err = "too many regions in 9-patch";
+ return {};
+ }
- ninePatch->regionColors.reserve(numRows * numCols);
- calculateRegionColors(rows, ninePatch->horizontalStretchRegions,
- ninePatch->verticalStretchRegions,
- width - 2, height - 2,
- &ninePatch->regionColors);
+ ninePatch->regionColors.reserve(numRows * numCols);
+ calculateRegionColors(rows, ninePatch->horizontalStretchRegions,
+ ninePatch->verticalStretchRegions, width - 2,
+ height - 2, &ninePatch->regionColors);
- // Compute the outline based on opacity.
+ // Compute the outline based on opacity.
- // Find left and right extent of 9-patch content on center row.
- HorizontalImageLine midRow(rows, 1, height / 2, width - 2);
- findOutlineInsets(&midRow, &ninePatch->outline.left, &ninePatch->outline.right);
+ // Find left and right extent of 9-patch content on center row.
+ HorizontalImageLine midRow(rows, 1, height / 2, width - 2);
+ findOutlineInsets(&midRow, &ninePatch->outline.left,
+ &ninePatch->outline.right);
- // Find top and bottom extent of 9-patch content on center column.
- VerticalImageLine midCol(rows, width / 2, 1, height - 2);
- findOutlineInsets(&midCol, &ninePatch->outline.top, &ninePatch->outline.bottom);
+ // Find top and bottom extent of 9-patch content on center column.
+ VerticalImageLine midCol(rows, width / 2, 1, height - 2);
+ findOutlineInsets(&midCol, &ninePatch->outline.top,
+ &ninePatch->outline.bottom);
- const int32_t outlineWidth = (width - 2) - ninePatch->outline.left - ninePatch->outline.right;
- const int32_t outlineHeight = (height - 2) - ninePatch->outline.top - ninePatch->outline.bottom;
+ const int32_t outlineWidth =
+ (width - 2) - ninePatch->outline.left - ninePatch->outline.right;
+ const int32_t outlineHeight =
+ (height - 2) - ninePatch->outline.top - ninePatch->outline.bottom;
- // Find the largest alpha value within the outline area.
- HorizontalImageLine outlineMidRow(rows,
- 1 + ninePatch->outline.left,
- 1 + ninePatch->outline.top + (outlineHeight / 2),
- outlineWidth);
- VerticalImageLine outlineMidCol(rows,
- 1 + ninePatch->outline.left + (outlineWidth / 2),
- 1 + ninePatch->outline.top,
- outlineHeight);
- ninePatch->outlineAlpha = std::max(findMaxAlpha(&outlineMidRow), findMaxAlpha(&outlineMidCol));
+ // Find the largest alpha value within the outline area.
+ HorizontalImageLine outlineMidRow(
+ rows, 1 + ninePatch->outline.left,
+ 1 + ninePatch->outline.top + (outlineHeight / 2), outlineWidth);
+ VerticalImageLine outlineMidCol(
+ rows, 1 + ninePatch->outline.left + (outlineWidth / 2),
+ 1 + ninePatch->outline.top, outlineHeight);
+ ninePatch->outlineAlpha =
+ std::max(findMaxAlpha(&outlineMidRow), findMaxAlpha(&outlineMidCol));
- // Assuming the image is a round rect, compute the radius by marching
- // diagonally from the top left corner towards the center.
- DiagonalImageLine diagonal(rows, 1 + ninePatch->outline.left, 1 + ninePatch->outline.top,
- 1, 1, std::min(outlineWidth, outlineHeight));
- int32_t topLeft, bottomRight;
- findOutlineInsets(&diagonal, &topLeft, &bottomRight);
+ // Assuming the image is a round rect, compute the radius by marching
+ // diagonally from the top left corner towards the center.
+ DiagonalImageLine diagonal(rows, 1 + ninePatch->outline.left,
+ 1 + ninePatch->outline.top, 1, 1,
+ std::min(outlineWidth, outlineHeight));
+ int32_t topLeft, bottomRight;
+ findOutlineInsets(&diagonal, &topLeft, &bottomRight);
- /* Determine source radius based upon inset:
- * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
- * sqrt(2) * r = sqrt(2) * i + r
- * (sqrt(2) - 1) * r = sqrt(2) * i
- * r = sqrt(2) / (sqrt(2) - 1) * i
- */
- ninePatch->outlineRadius = 3.4142f * topLeft;
- return ninePatch;
+ /* Determine source radius based upon inset:
+ * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
+ * sqrt(2) * r = sqrt(2) * i + r
+ * (sqrt(2) - 1) * r = sqrt(2) * i
+ * r = sqrt(2) / (sqrt(2) - 1) * i
+ */
+ ninePatch->outlineRadius = 3.4142f * topLeft;
+ return ninePatch;
}
std::unique_ptr<uint8_t[]> NinePatch::serializeBase(size_t* outLen) const {
- android::Res_png_9patch data;
- data.numXDivs = static_cast<uint8_t>(horizontalStretchRegions.size()) * 2;
- data.numYDivs = static_cast<uint8_t>(verticalStretchRegions.size()) * 2;
- data.numColors = static_cast<uint8_t>(regionColors.size());
- data.paddingLeft = padding.left;
- data.paddingRight = padding.right;
- data.paddingTop = padding.top;
- data.paddingBottom = padding.bottom;
+ android::Res_png_9patch data;
+ data.numXDivs = static_cast<uint8_t>(horizontalStretchRegions.size()) * 2;
+ data.numYDivs = static_cast<uint8_t>(verticalStretchRegions.size()) * 2;
+ data.numColors = static_cast<uint8_t>(regionColors.size());
+ data.paddingLeft = padding.left;
+ data.paddingRight = padding.right;
+ data.paddingTop = padding.top;
+ data.paddingBottom = padding.bottom;
- auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
- android::Res_png_9patch::serialize(data,
- (const int32_t*) horizontalStretchRegions.data(),
- (const int32_t*) verticalStretchRegions.data(),
- regionColors.data(),
- buffer.get());
- // Convert to file endianness.
- reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
+ auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
+ android::Res_png_9patch::serialize(
+ data, (const int32_t*)horizontalStretchRegions.data(),
+ (const int32_t*)verticalStretchRegions.data(), regionColors.data(),
+ buffer.get());
+ // Convert to file endianness.
+ reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
- *outLen = data.serializedSize();
- return buffer;
+ *outLen = data.serializedSize();
+ return buffer;
}
-std::unique_ptr<uint8_t[]> NinePatch::serializeLayoutBounds(size_t* outLen) const {
- size_t chunkLen = sizeof(uint32_t) * 4;
- auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]);
- uint8_t* cursor = buffer.get();
+std::unique_ptr<uint8_t[]> NinePatch::serializeLayoutBounds(
+ size_t* outLen) const {
+ size_t chunkLen = sizeof(uint32_t) * 4;
+ auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]);
+ uint8_t* cursor = buffer.get();
- memcpy(cursor, &layoutBounds.left, sizeof(layoutBounds.left));
- cursor += sizeof(layoutBounds.left);
+ memcpy(cursor, &layoutBounds.left, sizeof(layoutBounds.left));
+ cursor += sizeof(layoutBounds.left);
- memcpy(cursor, &layoutBounds.top, sizeof(layoutBounds.top));
- cursor += sizeof(layoutBounds.top);
+ memcpy(cursor, &layoutBounds.top, sizeof(layoutBounds.top));
+ cursor += sizeof(layoutBounds.top);
- memcpy(cursor, &layoutBounds.right, sizeof(layoutBounds.right));
- cursor += sizeof(layoutBounds.right);
+ memcpy(cursor, &layoutBounds.right, sizeof(layoutBounds.right));
+ cursor += sizeof(layoutBounds.right);
- memcpy(cursor, &layoutBounds.bottom, sizeof(layoutBounds.bottom));
- cursor += sizeof(layoutBounds.bottom);
+ memcpy(cursor, &layoutBounds.bottom, sizeof(layoutBounds.bottom));
+ cursor += sizeof(layoutBounds.bottom);
- *outLen = chunkLen;
- return buffer;
+ *outLen = chunkLen;
+ return buffer;
}
-std::unique_ptr<uint8_t[]> NinePatch::serializeRoundedRectOutline(size_t* outLen) const {
- size_t chunkLen = sizeof(uint32_t) * 6;
- auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]);
- uint8_t* cursor = buffer.get();
+std::unique_ptr<uint8_t[]> NinePatch::serializeRoundedRectOutline(
+ size_t* outLen) const {
+ size_t chunkLen = sizeof(uint32_t) * 6;
+ auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]);
+ uint8_t* cursor = buffer.get();
- memcpy(cursor, &outline.left, sizeof(outline.left));
- cursor += sizeof(outline.left);
+ memcpy(cursor, &outline.left, sizeof(outline.left));
+ cursor += sizeof(outline.left);
- memcpy(cursor, &outline.top, sizeof(outline.top));
- cursor += sizeof(outline.top);
+ memcpy(cursor, &outline.top, sizeof(outline.top));
+ cursor += sizeof(outline.top);
- memcpy(cursor, &outline.right, sizeof(outline.right));
- cursor += sizeof(outline.right);
+ memcpy(cursor, &outline.right, sizeof(outline.right));
+ cursor += sizeof(outline.right);
- memcpy(cursor, &outline.bottom, sizeof(outline.bottom));
- cursor += sizeof(outline.bottom);
+ memcpy(cursor, &outline.bottom, sizeof(outline.bottom));
+ cursor += sizeof(outline.bottom);
- *((float*) cursor) = outlineRadius;
- cursor += sizeof(outlineRadius);
+ *((float*)cursor) = outlineRadius;
+ cursor += sizeof(outlineRadius);
- *((uint32_t*) cursor) = outlineAlpha;
+ *((uint32_t*)cursor) = outlineAlpha;
- *outLen = chunkLen;
- return buffer;
+ *outLen = chunkLen;
+ return buffer;
}
::std::ostream& operator<<(::std::ostream& out, const Range& range) {
- return out << "[" << range.start << ", " << range.end << ")";
+ return out << "[" << range.start << ", " << range.end << ")";
}
::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) {
- return out << "l=" << bounds.left
- << " t=" << bounds.top
- << " r=" << bounds.right
- << " b=" << bounds.bottom;
+ return out << "l=" << bounds.left << " t=" << bounds.top
+ << " r=" << bounds.right << " b=" << bounds.bottom;
}
::std::ostream& operator<<(::std::ostream& out, const NinePatch& ninePatch) {
- return out << "horizontalStretch:" << util::joiner(ninePatch.horizontalStretchRegions, " ")
- << " verticalStretch:" << util::joiner(ninePatch.verticalStretchRegions, " ")
- << " padding: " << ninePatch.padding
- << ", bounds: " << ninePatch.layoutBounds
- << ", outline: " << ninePatch.outline
- << " rad=" << ninePatch.outlineRadius
- << " alpha=" << ninePatch.outlineAlpha;
+ return out << "horizontalStretch:"
+ << util::joiner(ninePatch.horizontalStretchRegions, " ")
+ << " verticalStretch:"
+ << util::joiner(ninePatch.verticalStretchRegions, " ")
+ << " padding: " << ninePatch.padding
+ << ", bounds: " << ninePatch.layoutBounds
+ << ", outline: " << ninePatch.outline
+ << " rad=" << ninePatch.outlineRadius
+ << " alpha=" << ninePatch.outlineAlpha;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/NinePatch_test.cpp b/tools/aapt2/compile/NinePatch_test.cpp
index 3106ff8..b8eda09 100644
--- a/tools/aapt2/compile/NinePatch_test.cpp
+++ b/tools/aapt2/compile/NinePatch_test.cpp
@@ -21,8 +21,8 @@
// Pixels are in RGBA_8888 packing.
-#define RED "\xff\x00\x00\xff"
-#define BLUE "\x00\x00\xff\xff"
+#define RED "\xff\x00\x00\xff"
+#define BLUE "\x00\x00\xff\xff"
#define GREEN "\xff\x00\x00\xff"
#define GR_70 "\xff\x00\x00\xb3"
#define GR_50 "\xff\x00\x00\x80"
@@ -32,327 +32,346 @@
#define TRANS "\x00\x00\x00\x00"
static uint8_t* k2x2[] = {
- (uint8_t*) WHITE WHITE,
- (uint8_t*) WHITE WHITE,
+ (uint8_t*)WHITE WHITE, (uint8_t*)WHITE WHITE,
};
static uint8_t* kMixedNeutralColor3x3[] = {
- (uint8_t*) WHITE BLACK TRANS,
- (uint8_t*) TRANS RED TRANS,
- (uint8_t*) WHITE WHITE WHITE,
+ (uint8_t*)WHITE BLACK TRANS, (uint8_t*)TRANS RED TRANS,
+ (uint8_t*)WHITE WHITE WHITE,
};
static uint8_t* kTransparentNeutralColor3x3[] = {
- (uint8_t*) TRANS BLACK TRANS,
- (uint8_t*) BLACK RED BLACK,
- (uint8_t*) TRANS BLACK TRANS,
+ (uint8_t*)TRANS BLACK TRANS, (uint8_t*)BLACK RED BLACK,
+ (uint8_t*)TRANS BLACK TRANS,
};
static uint8_t* kSingleStretch7x6[] = {
- (uint8_t*) WHITE WHITE BLACK BLACK BLACK WHITE WHITE,
- (uint8_t*) WHITE RED RED RED RED RED WHITE,
- (uint8_t*) BLACK RED RED RED RED RED WHITE,
- (uint8_t*) BLACK RED RED RED RED RED WHITE,
- (uint8_t*) WHITE RED RED RED RED RED WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE BLACK BLACK BLACK WHITE WHITE,
+ (uint8_t*)WHITE RED RED RED RED RED WHITE,
+ (uint8_t*)BLACK RED RED RED RED RED WHITE,
+ (uint8_t*)BLACK RED RED RED RED RED WHITE,
+ (uint8_t*)WHITE RED RED RED RED RED WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
};
static uint8_t* kMultipleStretch10x7[] = {
- (uint8_t*) WHITE WHITE BLACK WHITE BLACK BLACK WHITE BLACK WHITE WHITE,
- (uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
- (uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
- (uint8_t*) WHITE RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
- (uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
- (uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE BLACK WHITE BLACK BLACK WHITE BLACK WHITE WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)WHITE RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
};
static uint8_t* kPadding6x5[] = {
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE BLACK,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE BLACK BLACK WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE BLACK,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE BLACK BLACK WHITE WHITE,
};
static uint8_t* kLayoutBoundsWrongEdge3x3[] = {
- (uint8_t*) WHITE RED WHITE,
- (uint8_t*) RED WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE,
+ (uint8_t*)WHITE RED WHITE, (uint8_t*)RED WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE,
};
static uint8_t* kLayoutBoundsNotEdgeAligned5x5[] = {
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE RED,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE RED WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE RED WHITE WHITE,
};
static uint8_t* kLayoutBounds5x5[] = {
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE RED,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE RED,
- (uint8_t*) WHITE RED WHITE RED WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE RED WHITE RED WHITE,
};
static uint8_t* kAsymmetricLayoutBounds5x5[] = {
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE RED,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE RED WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE RED WHITE WHITE WHITE,
};
static uint8_t* kPaddingAndLayoutBounds5x5[] = {
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE RED,
- (uint8_t*) WHITE WHITE WHITE WHITE BLACK,
- (uint8_t*) WHITE WHITE WHITE WHITE RED,
- (uint8_t*) WHITE RED BLACK RED WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE BLACK,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE RED BLACK RED WHITE,
};
static uint8_t* kColorfulImage5x5[] = {
- (uint8_t*) WHITE BLACK WHITE BLACK WHITE,
- (uint8_t*) BLACK RED BLUE GREEN WHITE,
- (uint8_t*) BLACK RED GREEN GREEN WHITE,
- (uint8_t*) WHITE TRANS BLUE GREEN WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE BLACK WHITE BLACK WHITE,
+ (uint8_t*)BLACK RED BLUE GREEN WHITE,
+ (uint8_t*)BLACK RED GREEN GREEN WHITE,
+ (uint8_t*)WHITE TRANS BLUE GREEN WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
};
static uint8_t* kOutlineOpaque10x10[] = {
- (uint8_t*) WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
};
static uint8_t* kOutlineTranslucent10x10[] = {
- (uint8_t*) WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
- (uint8_t*) WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
};
static uint8_t* kOutlineOffsetTranslucent12x10[] = {
- (uint8_t*) WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)
+ WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
};
static uint8_t* kOutlineRadius5x5[] = {
- (uint8_t*) WHITE BLACK BLACK BLACK WHITE,
- (uint8_t*) BLACK TRANS GREEN TRANS WHITE,
- (uint8_t*) BLACK GREEN GREEN GREEN WHITE,
- (uint8_t*) BLACK TRANS GREEN TRANS WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE BLACK BLACK BLACK WHITE,
+ (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
+ (uint8_t*)BLACK GREEN GREEN GREEN WHITE,
+ (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
};
static uint8_t* kStretchAndPadding5x5[] = {
- (uint8_t*) WHITE WHITE BLACK WHITE WHITE,
- (uint8_t*) WHITE RED RED RED WHITE,
- (uint8_t*) BLACK RED RED RED BLACK,
- (uint8_t*) WHITE RED RED RED WHITE,
- (uint8_t*) WHITE WHITE BLACK WHITE WHITE,
+ (uint8_t*)WHITE WHITE BLACK WHITE WHITE, (uint8_t*)WHITE RED RED RED WHITE,
+ (uint8_t*)BLACK RED RED RED BLACK, (uint8_t*)WHITE RED RED RED WHITE,
+ (uint8_t*)WHITE WHITE BLACK WHITE WHITE,
};
TEST(NinePatchTest, Minimum3x3) {
- std::string err;
- EXPECT_EQ(nullptr, NinePatch::create(k2x2, 2, 2, &err));
- EXPECT_FALSE(err.empty());
+ std::string err;
+ EXPECT_EQ(nullptr, NinePatch::create(k2x2, 2, 2, &err));
+ EXPECT_FALSE(err.empty());
}
TEST(NinePatchTest, MixedNeutralColors) {
- std::string err;
- EXPECT_EQ(nullptr, NinePatch::create(kMixedNeutralColor3x3, 3, 3, &err));
- EXPECT_FALSE(err.empty());
+ std::string err;
+ EXPECT_EQ(nullptr, NinePatch::create(kMixedNeutralColor3x3, 3, 3, &err));
+ EXPECT_FALSE(err.empty());
}
TEST(NinePatchTest, TransparentNeutralColor) {
- std::string err;
- EXPECT_NE(nullptr, NinePatch::create(kTransparentNeutralColor3x3, 3, 3, &err));
+ std::string err;
+ EXPECT_NE(nullptr,
+ NinePatch::create(kTransparentNeutralColor3x3, 3, 3, &err));
}
TEST(NinePatchTest, SingleStretchRegion) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kSingleStretch7x6, 7, 6, &err);
- ASSERT_NE(nullptr, ninePatch);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kSingleStretch7x6, 7, 6, &err);
+ ASSERT_NE(nullptr, ninePatch);
- ASSERT_EQ(1u, ninePatch->horizontalStretchRegions.size());
- ASSERT_EQ(1u, ninePatch->verticalStretchRegions.size());
+ ASSERT_EQ(1u, ninePatch->horizontalStretchRegions.size());
+ ASSERT_EQ(1u, ninePatch->verticalStretchRegions.size());
- EXPECT_EQ(Range(1, 4), ninePatch->horizontalStretchRegions.front());
- EXPECT_EQ(Range(1, 3), ninePatch->verticalStretchRegions.front());
+ EXPECT_EQ(Range(1, 4), ninePatch->horizontalStretchRegions.front());
+ EXPECT_EQ(Range(1, 3), ninePatch->verticalStretchRegions.front());
}
TEST(NinePatchTest, MultipleStretchRegions) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kMultipleStretch10x7, 10, 7, &err);
- ASSERT_NE(nullptr, ninePatch);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kMultipleStretch10x7, 10, 7, &err);
+ ASSERT_NE(nullptr, ninePatch);
- ASSERT_EQ(3u, ninePatch->horizontalStretchRegions.size());
- ASSERT_EQ(2u, ninePatch->verticalStretchRegions.size());
+ ASSERT_EQ(3u, ninePatch->horizontalStretchRegions.size());
+ ASSERT_EQ(2u, ninePatch->verticalStretchRegions.size());
- EXPECT_EQ(Range(1, 2), ninePatch->horizontalStretchRegions[0]);
- EXPECT_EQ(Range(3, 5), ninePatch->horizontalStretchRegions[1]);
- EXPECT_EQ(Range(6, 7), ninePatch->horizontalStretchRegions[2]);
+ EXPECT_EQ(Range(1, 2), ninePatch->horizontalStretchRegions[0]);
+ EXPECT_EQ(Range(3, 5), ninePatch->horizontalStretchRegions[1]);
+ EXPECT_EQ(Range(6, 7), ninePatch->horizontalStretchRegions[2]);
- EXPECT_EQ(Range(0, 2), ninePatch->verticalStretchRegions[0]);
- EXPECT_EQ(Range(3, 5), ninePatch->verticalStretchRegions[1]);
+ EXPECT_EQ(Range(0, 2), ninePatch->verticalStretchRegions[0]);
+ EXPECT_EQ(Range(3, 5), ninePatch->verticalStretchRegions[1]);
}
TEST(NinePatchTest, InferPaddingFromStretchRegions) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kMultipleStretch10x7, 10, 7, &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(1, 0, 1, 0), ninePatch->padding);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kMultipleStretch10x7, 10, 7, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(1, 0, 1, 0), ninePatch->padding);
}
TEST(NinePatchTest, Padding) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kPadding6x5, 6, 5, &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kPadding6x5, 6, 5, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding);
}
TEST(NinePatchTest, LayoutBoundsAreOnWrongEdge) {
- std::string err;
- EXPECT_EQ(nullptr, NinePatch::create(kLayoutBoundsWrongEdge3x3, 3, 3, &err));
- EXPECT_FALSE(err.empty());
+ std::string err;
+ EXPECT_EQ(nullptr, NinePatch::create(kLayoutBoundsWrongEdge3x3, 3, 3, &err));
+ EXPECT_FALSE(err.empty());
}
TEST(NinePatchTest, LayoutBoundsMustTouchEdges) {
- std::string err;
- EXPECT_EQ(nullptr, NinePatch::create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err));
- EXPECT_FALSE(err.empty());
+ std::string err;
+ EXPECT_EQ(nullptr,
+ NinePatch::create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err));
+ EXPECT_FALSE(err.empty());
}
TEST(NinePatchTest, LayoutBounds) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kLayoutBounds5x5, 5, 5, &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kLayoutBounds5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds);
- ninePatch = NinePatch::create(kAsymmetricLayoutBounds5x5, 5, 5, &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(1, 1, 0, 0), ninePatch->layoutBounds);
+ ninePatch = NinePatch::create(kAsymmetricLayoutBounds5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(1, 1, 0, 0), ninePatch->layoutBounds);
}
TEST(NinePatchTest, PaddingAndLayoutBounds) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kPaddingAndLayoutBounds5x5, 5, 5,
- &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding);
- EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kPaddingAndLayoutBounds5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds);
}
TEST(NinePatchTest, RegionColorsAreCorrect) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kColorfulImage5x5, 5, 5, &err);
- ASSERT_NE(nullptr, ninePatch);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kColorfulImage5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, ninePatch);
- std::vector<uint32_t> expectedColors = {
- NinePatch::packRGBA((uint8_t*) RED),
- (uint32_t) android::Res_png_9patch::NO_COLOR,
- NinePatch::packRGBA((uint8_t*) GREEN),
- (uint32_t) android::Res_png_9patch::TRANSPARENT_COLOR,
- NinePatch::packRGBA((uint8_t*) BLUE),
- NinePatch::packRGBA((uint8_t*) GREEN),
- };
- EXPECT_EQ(expectedColors, ninePatch->regionColors);
+ std::vector<uint32_t> expectedColors = {
+ NinePatch::packRGBA((uint8_t*)RED),
+ (uint32_t)android::Res_png_9patch::NO_COLOR,
+ NinePatch::packRGBA((uint8_t*)GREEN),
+ (uint32_t)android::Res_png_9patch::TRANSPARENT_COLOR,
+ NinePatch::packRGBA((uint8_t*)BLUE),
+ NinePatch::packRGBA((uint8_t*)GREEN),
+ };
+ EXPECT_EQ(expectedColors, ninePatch->regionColors);
}
TEST(NinePatchTest, OutlineFromOpaqueImage) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineOpaque10x10, 10, 10, &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(2, 2, 2, 2), ninePatch->outline);
- EXPECT_EQ(0x000000ffu, ninePatch->outlineAlpha);
- EXPECT_EQ(0.0f, ninePatch->outlineRadius);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kOutlineOpaque10x10, 10, 10, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(2, 2, 2, 2), ninePatch->outline);
+ EXPECT_EQ(0x000000ffu, ninePatch->outlineAlpha);
+ EXPECT_EQ(0.0f, ninePatch->outlineRadius);
}
TEST(NinePatchTest, OutlineFromTranslucentImage) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineTranslucent10x10, 10, 10,
- &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(3, 3, 3, 3), ninePatch->outline);
- EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha);
- EXPECT_EQ(0.0f, ninePatch->outlineRadius);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kOutlineTranslucent10x10, 10, 10, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(3, 3, 3, 3), ninePatch->outline);
+ EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha);
+ EXPECT_EQ(0.0f, ninePatch->outlineRadius);
}
TEST(NinePatchTest, OutlineFromOffCenterImage) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineOffsetTranslucent12x10, 12, 10,
- &err);
- ASSERT_NE(nullptr, ninePatch);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kOutlineOffsetTranslucent12x10, 12, 10, &err);
+ ASSERT_NE(nullptr, ninePatch);
- // TODO(adamlesinski): The old AAPT algorithm searches from the outside to the middle
- // for each inset. If the outline is shifted, the search may not find a closer bounds.
- // This check should be:
- // EXPECT_EQ(Bounds(5, 3, 3, 3), ninePatch->outline);
- // but until I know what behaviour I'm breaking, I will leave it at the incorrect:
- EXPECT_EQ(Bounds(4, 3, 3, 3), ninePatch->outline);
+ // TODO(adamlesinski): The old AAPT algorithm searches from the outside to the
+ // middle
+ // for each inset. If the outline is shifted, the search may not find a closer
+ // bounds.
+ // This check should be:
+ // EXPECT_EQ(Bounds(5, 3, 3, 3), ninePatch->outline);
+ // but until I know what behaviour I'm breaking, I will leave it at the
+ // incorrect:
+ EXPECT_EQ(Bounds(4, 3, 3, 3), ninePatch->outline);
- EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha);
- EXPECT_EQ(0.0f, ninePatch->outlineRadius);
+ EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha);
+ EXPECT_EQ(0.0f, ninePatch->outlineRadius);
}
TEST(NinePatchTest, OutlineRadius) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineRadius5x5, 5, 5, &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(0, 0, 0, 0), ninePatch->outline);
- EXPECT_EQ(3.4142f, ninePatch->outlineRadius);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kOutlineRadius5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(0, 0, 0, 0), ninePatch->outline);
+ EXPECT_EQ(3.4142f, ninePatch->outlineRadius);
}
::testing::AssertionResult bigEndianOne(uint8_t* cursor) {
- if (cursor[0] == 0 && cursor[1] == 0 && cursor[2] == 0 && cursor[3] == 1) {
- return ::testing::AssertionSuccess();
- }
- return ::testing::AssertionFailure() << "Not BigEndian 1";
+ if (cursor[0] == 0 && cursor[1] == 0 && cursor[2] == 0 && cursor[3] == 1) {
+ return ::testing::AssertionSuccess();
+ }
+ return ::testing::AssertionFailure() << "Not BigEndian 1";
}
TEST(NinePatchTest, SerializePngEndianness) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kStretchAndPadding5x5, 5, 5, &err);
- ASSERT_NE(nullptr, ninePatch);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kStretchAndPadding5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, ninePatch);
- size_t len;
- std::unique_ptr<uint8_t[]> data = ninePatch->serializeBase(&len);
- ASSERT_NE(nullptr, data);
- ASSERT_NE(0u, len);
+ size_t len;
+ std::unique_ptr<uint8_t[]> data = ninePatch->serializeBase(&len);
+ ASSERT_NE(nullptr, data);
+ ASSERT_NE(0u, len);
- // Skip past wasDeserialized + numXDivs + numYDivs + numColors + xDivsOffset + yDivsOffset
- // (12 bytes)
- uint8_t* cursor = data.get() + 12;
+ // Skip past wasDeserialized + numXDivs + numYDivs + numColors + xDivsOffset +
+ // yDivsOffset
+ // (12 bytes)
+ uint8_t* cursor = data.get() + 12;
- // Check that padding is big-endian. Expecting value 1.
- EXPECT_TRUE(bigEndianOne(cursor));
- EXPECT_TRUE(bigEndianOne(cursor + 4));
- EXPECT_TRUE(bigEndianOne(cursor + 8));
- EXPECT_TRUE(bigEndianOne(cursor + 12));
+ // Check that padding is big-endian. Expecting value 1.
+ EXPECT_TRUE(bigEndianOne(cursor));
+ EXPECT_TRUE(bigEndianOne(cursor + 4));
+ EXPECT_TRUE(bigEndianOne(cursor + 8));
+ EXPECT_TRUE(bigEndianOne(cursor + 12));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp
index 055d8b5..9b5fa7e09 100644
--- a/tools/aapt2/compile/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-#include "util/BigBuffer.h"
#include "Png.h"
#include "Source.h"
+#include "util/BigBuffer.h"
#include "util/Util.h"
#include <androidfw/ResourceTypes.h>
-#include <iostream>
#include <png.h>
+#include <zlib.h>
+#include <iostream>
#include <sstream>
#include <string>
#include <vector>
-#include <zlib.h>
namespace aapt {
@@ -33,158 +33,166 @@
constexpr size_t kPngSignatureSize = 8u;
struct PngInfo {
- ~PngInfo() {
- for (png_bytep row : rows) {
- if (row != nullptr) {
- delete[] row;
- }
- }
-
- delete[] xDivs;
- delete[] yDivs;
+ ~PngInfo() {
+ for (png_bytep row : rows) {
+ if (row != nullptr) {
+ delete[] row;
+ }
}
- void* serialize9Patch() {
- void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs, yDivs,
- colors.data());
- reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile();
- return serialized;
- }
+ delete[] xDivs;
+ delete[] yDivs;
+ }
- uint32_t width = 0;
- uint32_t height = 0;
- std::vector<png_bytep> rows;
+ void* serialize9Patch() {
+ void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs,
+ yDivs, colors.data());
+ reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile();
+ return serialized;
+ }
- bool is9Patch = false;
- android::Res_png_9patch info9Patch;
- int32_t* xDivs = nullptr;
- int32_t* yDivs = nullptr;
- std::vector<uint32_t> colors;
+ uint32_t width = 0;
+ uint32_t height = 0;
+ std::vector<png_bytep> rows;
- // Layout padding.
- bool haveLayoutBounds = false;
- int32_t layoutBoundsLeft;
- int32_t layoutBoundsTop;
- int32_t layoutBoundsRight;
- int32_t layoutBoundsBottom;
+ bool is9Patch = false;
+ android::Res_png_9patch info9Patch;
+ int32_t* xDivs = nullptr;
+ int32_t* yDivs = nullptr;
+ std::vector<uint32_t> colors;
- // Round rect outline description.
- int32_t outlineInsetsLeft;
- int32_t outlineInsetsTop;
- int32_t outlineInsetsRight;
- int32_t outlineInsetsBottom;
- float outlineRadius;
- uint8_t outlineAlpha;
+ // Layout padding.
+ bool haveLayoutBounds = false;
+ int32_t layoutBoundsLeft;
+ int32_t layoutBoundsTop;
+ int32_t layoutBoundsRight;
+ int32_t layoutBoundsBottom;
+
+ // Round rect outline description.
+ int32_t outlineInsetsLeft;
+ int32_t outlineInsetsTop;
+ int32_t outlineInsetsRight;
+ int32_t outlineInsetsBottom;
+ float outlineRadius;
+ uint8_t outlineAlpha;
};
-static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t length) {
- std::istream* input = reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
- if (!input->read(reinterpret_cast<char*>(data), length)) {
- png_error(readPtr, strerror(errno));
- }
+static void readDataFromStream(png_structp readPtr, png_bytep data,
+ png_size_t length) {
+ std::istream* input =
+ reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
+ if (!input->read(reinterpret_cast<char*>(data), length)) {
+ png_error(readPtr, strerror(errno));
+ }
}
-static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) {
- BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
- png_bytep buf = outBuffer->nextBlock<png_byte>(length);
- memcpy(buf, data, length);
+static void writeDataToStream(png_structp writePtr, png_bytep data,
+ png_size_t length) {
+ BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
+ png_bytep buf = outBuffer->nextBlock<png_byte>(length);
+ memcpy(buf, data, length);
}
-static void flushDataToStream(png_structp /*writePtr*/) {
-}
+static void flushDataToStream(png_structp /*writePtr*/) {}
static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
- IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
- diag->warn(DiagMessage() << warningMessage);
+ IDiagnostics* diag =
+ reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
+ diag->warn(DiagMessage() << warningMessage);
}
+static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr,
+ PngInfo* outInfo) {
+ if (setjmp(png_jmpbuf(readPtr))) {
+ diag->error(DiagMessage() << "failed reading png");
+ return false;
+ }
-static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) {
- if (setjmp(png_jmpbuf(readPtr))) {
- diag->error(DiagMessage() << "failed reading png");
- return false;
- }
+ png_set_sig_bytes(readPtr, kPngSignatureSize);
+ png_read_info(readPtr, infoPtr);
- png_set_sig_bytes(readPtr, kPngSignatureSize);
- png_read_info(readPtr, infoPtr);
+ int colorType, bitDepth, interlaceType, compressionType;
+ png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth,
+ &colorType, &interlaceType, &compressionType, nullptr);
- int colorType, bitDepth, interlaceType, compressionType;
- png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, &colorType,
- &interlaceType, &compressionType, nullptr);
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ png_set_palette_to_rgb(readPtr);
+ }
- if (colorType == PNG_COLOR_TYPE_PALETTE) {
- png_set_palette_to_rgb(readPtr);
- }
+ if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
+ png_set_expand_gray_1_2_4_to_8(readPtr);
+ }
- if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
- png_set_expand_gray_1_2_4_to_8(readPtr);
- }
+ if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
+ png_set_tRNS_to_alpha(readPtr);
+ }
- if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
- png_set_tRNS_to_alpha(readPtr);
- }
+ if (bitDepth == 16) {
+ png_set_strip_16(readPtr);
+ }
- if (bitDepth == 16) {
- png_set_strip_16(readPtr);
- }
+ if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
+ png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
+ }
- if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
- png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
- }
+ if (colorType == PNG_COLOR_TYPE_GRAY ||
+ colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(readPtr);
+ }
- if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
- png_set_gray_to_rgb(readPtr);
- }
+ png_set_interlace_handling(readPtr);
+ png_read_update_info(readPtr, infoPtr);
- png_set_interlace_handling(readPtr);
- png_read_update_info(readPtr, infoPtr);
+ const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
+ outInfo->rows.resize(outInfo->height);
+ for (size_t i = 0; i < outInfo->height; i++) {
+ outInfo->rows[i] = new png_byte[rowBytes];
+ }
- const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
- outInfo->rows.resize(outInfo->height);
- for (size_t i = 0; i < outInfo->height; i++) {
- outInfo->rows[i] = new png_byte[rowBytes];
- }
-
- png_read_image(readPtr, outInfo->rows.data());
- png_read_end(readPtr, infoPtr);
- return true;
+ png_read_image(readPtr, outInfo->rows.data());
+ png_read_end(readPtr, infoPtr);
+ return true;
}
-static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, void* data) {
- size_t patchSize = inPatch->serializedSize();
- void* newData = malloc(patchSize);
- memcpy(newData, data, patchSize);
- android::Res_png_9patch* outPatch = inPatch->deserialize(newData);
- outPatch->fileToDevice();
- // deserialization is done in place, so outPatch == newData
- assert(outPatch == newData);
- assert(outPatch->numXDivs == inPatch->numXDivs);
- assert(outPatch->numYDivs == inPatch->numYDivs);
- assert(outPatch->paddingLeft == inPatch->paddingLeft);
- assert(outPatch->paddingRight == inPatch->paddingRight);
- assert(outPatch->paddingTop == inPatch->paddingTop);
- assert(outPatch->paddingBottom == inPatch->paddingBottom);
-/* for (int i = 0; i < outPatch->numXDivs; i++) {
- assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]);
- }
- for (int i = 0; i < outPatch->numYDivs; i++) {
- assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]);
- }
- for (int i = 0; i < outPatch->numColors; i++) {
- assert(outPatch->getColors()[i] == inPatch->getColors()[i]);
- }*/
- free(newData);
+static void checkNinePatchSerialization(android::Res_png_9patch* inPatch,
+ void* data) {
+ size_t patchSize = inPatch->serializedSize();
+ void* newData = malloc(patchSize);
+ memcpy(newData, data, patchSize);
+ android::Res_png_9patch* outPatch = inPatch->deserialize(newData);
+ outPatch->fileToDevice();
+ // deserialization is done in place, so outPatch == newData
+ assert(outPatch == newData);
+ assert(outPatch->numXDivs == inPatch->numXDivs);
+ assert(outPatch->numYDivs == inPatch->numYDivs);
+ assert(outPatch->paddingLeft == inPatch->paddingLeft);
+ assert(outPatch->paddingRight == inPatch->paddingRight);
+ assert(outPatch->paddingTop == inPatch->paddingTop);
+ assert(outPatch->paddingBottom == inPatch->paddingBottom);
+ /* for (int i = 0; i < outPatch->numXDivs; i++) {
+ assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]);
+ }
+ for (int i = 0; i < outPatch->numYDivs; i++) {
+ assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]);
+ }
+ for (int i = 0; i < outPatch->numColors; i++) {
+ assert(outPatch->getColors()[i] == inPatch->getColors()[i]);
+ }*/
+ free(newData);
}
-/*static void dump_image(int w, int h, const png_byte* const* rows, int color_type) {
+/*static void dump_image(int w, int h, const png_byte* const* rows, int
+color_type) {
int i, j, rr, gg, bb, aa;
int bpp;
- if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
+ if (color_type == PNG_COLOR_TYPE_PALETTE || color_type ==
+PNG_COLOR_TYPE_GRAY) {
bpp = 1;
} else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
bpp = 2;
- } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
+ } else if (color_type == PNG_COLOR_TYPE_RGB || color_type ==
+PNG_COLOR_TYPE_RGB_ALPHA) {
// We use a padding byte even when there is no alpha
bpp = 4;
} else {
@@ -224,1055 +232,1083 @@
}
}*/
-#define MAX(a,b) ((a)>(b)?(a):(b))
-#define ABS(a) ((a)<0?-(a):(a))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define ABS(a) ((a) < 0 ? -(a) : (a))
-static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance,
- png_colorp rgbPalette, png_bytep alphaPalette,
- int *paletteEntries, bool *hasTransparency, int *colorType,
+static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo,
+ int grayscaleTolerance, png_colorp rgbPalette,
+ png_bytep alphaPalette, int* paletteEntries,
+ bool* hasTransparency, int* colorType,
png_bytepp outRows) {
- int w = imageInfo.width;
- int h = imageInfo.height;
- int i, j, rr, gg, bb, aa, idx;
- uint32_t colors[256], col;
- int num_colors = 0;
- int maxGrayDeviation = 0;
+ int w = imageInfo.width;
+ int h = imageInfo.height;
+ int i, j, rr, gg, bb, aa, idx;
+ uint32_t colors[256], col;
+ int num_colors = 0;
+ int maxGrayDeviation = 0;
- bool isOpaque = true;
- bool isPalette = true;
- bool isGrayscale = true;
+ bool isOpaque = true;
+ bool isPalette = true;
+ bool isGrayscale = true;
- // Scan the entire image and determine if:
- // 1. Every pixel has R == G == B (grayscale)
- // 2. Every pixel has A == 255 (opaque)
- // 3. There are no more than 256 distinct RGBA colors
+ // Scan the entire image and determine if:
+ // 1. Every pixel has R == G == B (grayscale)
+ // 2. Every pixel has A == 255 (opaque)
+ // 3. There are no more than 256 distinct RGBA colors
- if (kDebug) {
- printf("Initial image data:\n");
- //dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA);
- }
+ if (kDebug) {
+ printf("Initial image data:\n");
+ // dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA);
+ }
- for (j = 0; j < h; j++) {
- const png_byte* row = imageInfo.rows[j];
- png_bytep out = outRows[j];
- for (i = 0; i < w; i++) {
- rr = *row++;
- gg = *row++;
- bb = *row++;
- aa = *row++;
+ for (j = 0; j < h; j++) {
+ const png_byte* row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
- int odev = maxGrayDeviation;
- maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
- maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
- maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
- if (maxGrayDeviation > odev) {
- if (kDebug) {
- printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
- maxGrayDeviation, i, j, rr, gg, bb, aa);
- }
- }
-
- // Check if image is really grayscale
- if (isGrayscale) {
- if (rr != gg || rr != bb) {
- if (kDebug) {
- printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
- i, j, rr, gg, bb, aa);
- }
- isGrayscale = false;
- }
- }
-
- // Check if image is really opaque
- if (isOpaque) {
- if (aa != 0xff) {
- if (kDebug) {
- printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
- i, j, rr, gg, bb, aa);
- }
- isOpaque = false;
- }
- }
-
- // Check if image is really <= 256 colors
- if (isPalette) {
- col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
- bool match = false;
- for (idx = 0; idx < num_colors; idx++) {
- if (colors[idx] == col) {
- match = true;
- break;
- }
- }
-
- // Write the palette index for the pixel to outRows optimistically
- // We might overwrite it later if we decide to encode as gray or
- // gray + alpha
- *out++ = idx;
- if (!match) {
- if (num_colors == 256) {
- if (kDebug) {
- printf("Found 257th color at %d, %d\n", i, j);
- }
- isPalette = false;
- } else {
- colors[num_colors++] = col;
- }
- }
- }
+ int odev = maxGrayDeviation;
+ maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
+ if (maxGrayDeviation > odev) {
+ if (kDebug) {
+ printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
+ maxGrayDeviation, i, j, rr, gg, bb, aa);
}
- }
+ }
- *paletteEntries = 0;
- *hasTransparency = !isOpaque;
- int bpp = isOpaque ? 3 : 4;
- int paletteSize = w * h + bpp * num_colors;
-
- if (kDebug) {
- printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
- printf("isOpaque = %s\n", isOpaque ? "true" : "false");
- printf("isPalette = %s\n", isPalette ? "true" : "false");
- printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
- paletteSize, 2 * w * h, bpp * w * h);
- printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance);
- }
-
- // Choose the best color type for the image.
- // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
- // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
- // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
- // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
- // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
- if (isGrayscale) {
- if (isOpaque) {
- *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
- } else {
- // Use a simple heuristic to determine whether using a palette will
- // save space versus using gray + alpha for each pixel.
- // This doesn't take into account chunk overhead, filtering, LZ
- // compression, etc.
- if (isPalette && (paletteSize < 2 * w * h)) {
- *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
- } else {
- *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
- }
+ // Check if image is really grayscale
+ if (isGrayscale) {
+ if (rr != gg || rr != bb) {
+ if (kDebug) {
+ printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", i, j,
+ rr, gg, bb, aa);
+ }
+ isGrayscale = false;
}
- } else if (isPalette && (paletteSize < bpp * w * h)) {
- *colorType = PNG_COLOR_TYPE_PALETTE;
+ }
+
+ // Check if image is really opaque
+ if (isOpaque) {
+ if (aa != 0xff) {
+ if (kDebug) {
+ printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", i, j,
+ rr, gg, bb, aa);
+ }
+ isOpaque = false;
+ }
+ }
+
+ // Check if image is really <= 256 colors
+ if (isPalette) {
+ col = (uint32_t)((rr << 24) | (gg << 16) | (bb << 8) | aa);
+ bool match = false;
+ for (idx = 0; idx < num_colors; idx++) {
+ if (colors[idx] == col) {
+ match = true;
+ break;
+ }
+ }
+
+ // Write the palette index for the pixel to outRows optimistically
+ // We might overwrite it later if we decide to encode as gray or
+ // gray + alpha
+ *out++ = idx;
+ if (!match) {
+ if (num_colors == 256) {
+ if (kDebug) {
+ printf("Found 257th color at %d, %d\n", i, j);
+ }
+ isPalette = false;
+ } else {
+ colors[num_colors++] = col;
+ }
+ }
+ }
+ }
+ }
+
+ *paletteEntries = 0;
+ *hasTransparency = !isOpaque;
+ int bpp = isOpaque ? 3 : 4;
+ int paletteSize = w * h + bpp * num_colors;
+
+ if (kDebug) {
+ printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
+ printf("isOpaque = %s\n", isOpaque ? "true" : "false");
+ printf("isPalette = %s\n", isPalette ? "true" : "false");
+ printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", paletteSize,
+ 2 * w * h, bpp * w * h);
+ printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation,
+ grayscaleTolerance);
+ }
+
+ // Choose the best color type for the image.
+ // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
+ // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct
+ // combinations
+ // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
+ // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is
+ // sufficiently
+ // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
+ if (isGrayscale) {
+ if (isOpaque) {
+ *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
} else {
- if (maxGrayDeviation <= grayscaleTolerance) {
- diag->note(DiagMessage()
- << "forcing image to gray (max deviation = "
- << maxGrayDeviation << ")");
- *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
+ // Use a simple heuristic to determine whether using a palette will
+ // save space versus using gray + alpha for each pixel.
+ // This doesn't take into account chunk overhead, filtering, LZ
+ // compression, etc.
+ if (isPalette && (paletteSize < 2 * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
+ } else {
+ *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
+ }
+ }
+ } else if (isPalette && (paletteSize < bpp * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE;
+ } else {
+ if (maxGrayDeviation <= grayscaleTolerance) {
+ diag->note(DiagMessage() << "forcing image to gray (max deviation = "
+ << maxGrayDeviation << ")");
+ *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
+ } else {
+ *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+ }
+
+ // Perform postprocessing of the image or palette data based on the final
+ // color type chosen
+
+ if (*colorType == PNG_COLOR_TYPE_PALETTE) {
+ // Create separate RGB and Alpha palettes and set the number of colors
+ *paletteEntries = num_colors;
+
+ // Create the RGB and alpha palettes
+ for (int idx = 0; idx < num_colors; idx++) {
+ col = colors[idx];
+ rgbPalette[idx].red = (png_byte)((col >> 24) & 0xff);
+ rgbPalette[idx].green = (png_byte)((col >> 16) & 0xff);
+ rgbPalette[idx].blue = (png_byte)((col >> 8) & 0xff);
+ alphaPalette[idx] = (png_byte)(col & 0xff);
+ }
+ } else if (*colorType == PNG_COLOR_TYPE_GRAY ||
+ *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ // If the image is gray or gray + alpha, compact the pixels into outRows
+ for (j = 0; j < h; j++) {
+ const png_byte* row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
+
+ if (isGrayscale) {
+ *out++ = rr;
} else {
- *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+ *out++ = (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
}
+ if (!isOpaque) {
+ *out++ = aa;
+ }
+ }
}
-
- // Perform postprocessing of the image or palette data based on the final
- // color type chosen
-
- if (*colorType == PNG_COLOR_TYPE_PALETTE) {
- // Create separate RGB and Alpha palettes and set the number of colors
- *paletteEntries = num_colors;
-
- // Create the RGB and alpha palettes
- for (int idx = 0; idx < num_colors; idx++) {
- col = colors[idx];
- rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff);
- rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
- rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff);
- alphaPalette[idx] = (png_byte) (col & 0xff);
- }
- } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
- // If the image is gray or gray + alpha, compact the pixels into outRows
- for (j = 0; j < h; j++) {
- const png_byte* row = imageInfo.rows[j];
- png_bytep out = outRows[j];
- for (i = 0; i < w; i++) {
- rr = *row++;
- gg = *row++;
- bb = *row++;
- aa = *row++;
-
- if (isGrayscale) {
- *out++ = rr;
- } else {
- *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
- }
- if (!isOpaque) {
- *out++ = aa;
- }
- }
- }
- }
+ }
}
-static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info,
- int grayScaleTolerance) {
- if (setjmp(png_jmpbuf(writePtr))) {
- diag->error(DiagMessage() << "failed to write png");
- return false;
+static bool writePng(IDiagnostics* diag, png_structp writePtr,
+ png_infop infoPtr, PngInfo* info, int grayScaleTolerance) {
+ if (setjmp(png_jmpbuf(writePtr))) {
+ diag->error(DiagMessage() << "failed to write png");
+ return false;
+ }
+
+ uint32_t width, height;
+ int colorType, bitDepth, interlaceType, compressionType;
+
+ png_unknown_chunk unknowns[3];
+ unknowns[0].data = nullptr;
+ unknowns[1].data = nullptr;
+ unknowns[2].data = nullptr;
+
+ png_bytepp outRows =
+ (png_bytepp)malloc((int)info->height * sizeof(png_bytep));
+ if (outRows == (png_bytepp)0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
+ }
+ for (uint32_t i = 0; i < info->height; i++) {
+ outRows[i] = (png_bytep)malloc(2 * (int)info->width);
+ if (outRows[i] == (png_bytep)0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
}
+ }
- uint32_t width, height;
- int colorType, bitDepth, interlaceType, compressionType;
+ png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
- png_unknown_chunk unknowns[3];
- unknowns[0].data = nullptr;
- unknowns[1].data = nullptr;
- unknowns[2].data = nullptr;
+ if (kDebug) {
+ diag->note(DiagMessage() << "writing image: w = " << info->width
+ << ", h = " << info->height);
+ }
- png_bytepp outRows = (png_bytepp) malloc((int) info->height * sizeof(png_bytep));
- if (outRows == (png_bytepp) 0) {
- printf("Can't allocate output buffer!\n");
- exit(1);
- }
- for (uint32_t i = 0; i < info->height; i++) {
- outRows[i] = (png_bytep) malloc(2 * (int) info->width);
- if (outRows[i] == (png_bytep) 0) {
- printf("Can't allocate output buffer!\n");
- exit(1);
- }
- }
+ png_color rgbPalette[256];
+ png_byte alphaPalette[256];
+ bool hasTransparency;
+ int paletteEntries;
- png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
+ analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette,
+ &paletteEntries, &hasTransparency, &colorType, outRows);
- if (kDebug) {
+ // If the image is a 9-patch, we need to preserve it as a ARGB file to make
+ // sure the pixels will not be pre-dithered/clamped until we decide they are
+ if (info->is9Patch &&
+ (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY ||
+ colorType == PNG_COLOR_TYPE_PALETTE)) {
+ colorType = PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+
+ if (kDebug) {
+ switch (colorType) {
+ case PNG_COLOR_TYPE_PALETTE:
+ diag->note(DiagMessage() << "has " << paletteEntries << " colors"
+ << (hasTransparency ? " (with alpha)" : "")
+ << ", using PNG_COLOR_TYPE_PALLETTE");
+ break;
+ case PNG_COLOR_TYPE_GRAY:
diag->note(DiagMessage()
- << "writing image: w = " << info->width
- << ", h = " << info->height);
+ << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ diag->note(DiagMessage()
+ << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ diag->note(DiagMessage()
+ << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
+ break;
}
+ }
- png_color rgbPalette[256];
- png_byte alphaPalette[256];
- bool hasTransparency;
- int paletteEntries;
+ png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
- analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette,
- &paletteEntries, &hasTransparency, &colorType, outRows);
-
- // If the image is a 9-patch, we need to preserve it as a ARGB file to make
- // sure the pixels will not be pre-dithered/clamped until we decide they are
- if (info->is9Patch && (colorType == PNG_COLOR_TYPE_RGB ||
- colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_PALETTE)) {
- colorType = PNG_COLOR_TYPE_RGB_ALPHA;
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
+ if (hasTransparency) {
+ png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries,
+ (png_color_16p)0);
}
+ png_set_filter(writePtr, 0, PNG_NO_FILTERS);
+ } else {
+ png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
+ }
+ if (info->is9Patch) {
+ int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0);
+ int pIndex = info->haveLayoutBounds ? 2 : 1;
+ int bIndex = 1;
+ int oIndex = 0;
+
+ // Chunks ordered thusly because older platforms depend on the base 9 patch
+ // data being last
+ png_bytep chunkNames = info->haveLayoutBounds
+ ? (png_bytep) "npOl\0npLb\0npTc\0"
+ : (png_bytep) "npOl\0npTc";
+
+ // base 9 patch data
if (kDebug) {
- switch (colorType) {
- case PNG_COLOR_TYPE_PALETTE:
- diag->note(DiagMessage()
- << "has " << paletteEntries
- << " colors" << (hasTransparency ? " (with alpha)" : "")
- << ", using PNG_COLOR_TYPE_PALLETTE");
- break;
- case PNG_COLOR_TYPE_GRAY:
- diag->note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
- break;
- case PNG_COLOR_TYPE_GRAY_ALPHA:
- diag->note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
- break;
- case PNG_COLOR_TYPE_RGB:
- diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
- break;
- case PNG_COLOR_TYPE_RGB_ALPHA:
- diag->note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
- break;
- }
+ diag->note(DiagMessage() << "adding 9-patch info..");
+ }
+ strcpy((char*)unknowns[pIndex].name, "npTc");
+ unknowns[pIndex].data = (png_byte*)info->serialize9Patch();
+ unknowns[pIndex].size = info->info9Patch.serializedSize();
+ // TODO: remove the check below when everything works
+ checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data);
+
+ // automatically generated 9 patch outline data
+ int chunkSize = sizeof(png_uint_32) * 6;
+ strcpy((char*)unknowns[oIndex].name, "npOl");
+ unknowns[oIndex].data = (png_byte*)calloc(chunkSize, 1);
+ png_byte outputData[chunkSize];
+ memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
+ ((float*)outputData)[4] = info->outlineRadius;
+ ((png_uint_32*)outputData)[5] = info->outlineAlpha;
+ memcpy(unknowns[oIndex].data, &outputData, chunkSize);
+ unknowns[oIndex].size = chunkSize;
+
+ // optional optical inset / layout bounds data
+ if (info->haveLayoutBounds) {
+ int chunkSize = sizeof(png_uint_32) * 4;
+ strcpy((char*)unknowns[bIndex].name, "npLb");
+ unknowns[bIndex].data = (png_byte*)calloc(chunkSize, 1);
+ memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
+ unknowns[bIndex].size = chunkSize;
}
- png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType,
- PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
-
- if (colorType == PNG_COLOR_TYPE_PALETTE) {
- png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
- if (hasTransparency) {
- png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, (png_color_16p) 0);
- }
- png_set_filter(writePtr, 0, PNG_NO_FILTERS);
- } else {
- png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
+ for (int i = 0; i < chunkCount; i++) {
+ unknowns[i].location = PNG_HAVE_PLTE;
}
-
- if (info->is9Patch) {
- int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0);
- int pIndex = info->haveLayoutBounds ? 2 : 1;
- int bIndex = 1;
- int oIndex = 0;
-
- // Chunks ordered thusly because older platforms depend on the base 9 patch data being last
- png_bytep chunkNames = info->haveLayoutBounds
- ? (png_bytep)"npOl\0npLb\0npTc\0"
- : (png_bytep)"npOl\0npTc";
-
- // base 9 patch data
- if (kDebug) {
- diag->note(DiagMessage() << "adding 9-patch info..");
- }
- strcpy((char*)unknowns[pIndex].name, "npTc");
- unknowns[pIndex].data = (png_byte*) info->serialize9Patch();
- unknowns[pIndex].size = info->info9Patch.serializedSize();
- // TODO: remove the check below when everything works
- checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data);
-
- // automatically generated 9 patch outline data
- int chunkSize = sizeof(png_uint_32) * 6;
- strcpy((char*)unknowns[oIndex].name, "npOl");
- unknowns[oIndex].data = (png_byte*) calloc(chunkSize, 1);
- png_byte outputData[chunkSize];
- memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
- ((float*) outputData)[4] = info->outlineRadius;
- ((png_uint_32*) outputData)[5] = info->outlineAlpha;
- memcpy(unknowns[oIndex].data, &outputData, chunkSize);
- unknowns[oIndex].size = chunkSize;
-
- // optional optical inset / layout bounds data
- if (info->haveLayoutBounds) {
- int chunkSize = sizeof(png_uint_32) * 4;
- strcpy((char*)unknowns[bIndex].name, "npLb");
- unknowns[bIndex].data = (png_byte*) calloc(chunkSize, 1);
- memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
- unknowns[bIndex].size = chunkSize;
- }
-
- for (int i = 0; i < chunkCount; i++) {
- unknowns[i].location = PNG_HAVE_PLTE;
- }
- png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS,
- chunkNames, chunkCount);
- png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
+ png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, chunkNames,
+ chunkCount);
+ png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
#if PNG_LIBPNG_VER < 10600
- // Deal with unknown chunk location bug in 1.5.x and earlier.
- png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE);
- if (info->haveLayoutBounds) {
- png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE);
- }
+ // Deal with unknown chunk location bug in 1.5.x and earlier.
+ png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE);
+ if (info->haveLayoutBounds) {
+ png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE);
+ }
#endif
+ }
+
+ png_write_info(writePtr, infoPtr);
+
+ png_bytepp rows;
+ if (colorType == PNG_COLOR_TYPE_RGB ||
+ colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
+ if (colorType == PNG_COLOR_TYPE_RGB) {
+ png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
}
+ rows = info->rows.data();
+ } else {
+ rows = outRows;
+ }
+ png_write_image(writePtr, rows);
- png_write_info(writePtr, infoPtr);
+ if (kDebug) {
+ printf("Final image data:\n");
+ // dump_image(info->width, info->height, rows, colorType);
+ }
- png_bytepp rows;
- if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
- if (colorType == PNG_COLOR_TYPE_RGB) {
- png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
- }
- rows = info->rows.data();
- } else {
- rows = outRows;
- }
- png_write_image(writePtr, rows);
+ png_write_end(writePtr, infoPtr);
- if (kDebug) {
- printf("Final image data:\n");
- //dump_image(info->width, info->height, rows, colorType);
- }
+ for (uint32_t i = 0; i < info->height; i++) {
+ free(outRows[i]);
+ }
+ free(outRows);
+ free(unknowns[0].data);
+ free(unknowns[1].data);
+ free(unknowns[2].data);
- png_write_end(writePtr, infoPtr);
+ png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType,
+ &interlaceType, &compressionType, nullptr);
- for (uint32_t i = 0; i < info->height; i++) {
- free(outRows[i]);
- }
- free(outRows);
- free(unknowns[0].data);
- free(unknowns[1].data);
- free(unknowns[2].data);
-
- png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceType,
- &compressionType, nullptr);
-
- if (kDebug) {
- diag->note(DiagMessage()
- << "image written: w = " << width << ", h = " << height
- << ", d = " << bitDepth << ", colors = " << colorType
- << ", inter = " << interlaceType << ", comp = " << compressionType);
- }
- return true;
+ if (kDebug) {
+ diag->note(DiagMessage() << "image written: w = " << width
+ << ", h = " << height << ", d = " << bitDepth
+ << ", colors = " << colorType
+ << ", inter = " << interlaceType
+ << ", comp = " << compressionType);
+ }
+ return true;
}
constexpr uint32_t kColorWhite = 0xffffffffu;
constexpr uint32_t kColorTick = 0xff000000u;
constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu;
-enum class TickType {
- kNone,
- kTick,
- kLayoutBounds,
- kBoth
-};
+enum class TickType { kNone, kTick, kLayoutBounds, kBoth };
static TickType tickType(png_bytep p, bool transparent, const char** outError) {
- png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+ png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
- if (transparent) {
- if (p[3] == 0) {
- return TickType::kNone;
- }
- if (color == kColorLayoutBoundsTick) {
- return TickType::kLayoutBounds;
- }
- if (color == kColorTick) {
- return TickType::kTick;
- }
-
- // Error cases
- if (p[3] != 0xff) {
- *outError = "Frame pixels must be either solid or transparent "
- "(not intermediate alphas)";
- return TickType::kNone;
- }
-
- if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
- *outError = "Ticks in transparent frame must be black or red";
- }
- return TickType::kTick;
- }
-
- if (p[3] != 0xFF) {
- *outError = "White frame must be a solid color (no alpha)";
- }
- if (color == kColorWhite) {
- return TickType::kNone;
- }
- if (color == kColorTick) {
- return TickType::kTick;
+ if (transparent) {
+ if (p[3] == 0) {
+ return TickType::kNone;
}
if (color == kColorLayoutBoundsTick) {
- return TickType::kLayoutBounds;
+ return TickType::kLayoutBounds;
+ }
+ if (color == kColorTick) {
+ return TickType::kTick;
+ }
+
+ // Error cases
+ if (p[3] != 0xff) {
+ *outError =
+ "Frame pixels must be either solid or transparent "
+ "(not intermediate alphas)";
+ return TickType::kNone;
}
if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
- *outError = "Ticks in white frame must be black or red";
- return TickType::kNone;
+ *outError = "Ticks in transparent frame must be black or red";
}
return TickType::kTick;
+ }
+
+ if (p[3] != 0xFF) {
+ *outError = "White frame must be a solid color (no alpha)";
+ }
+ if (color == kColorWhite) {
+ return TickType::kNone;
+ }
+ if (color == kColorTick) {
+ return TickType::kTick;
+ }
+ if (color == kColorLayoutBoundsTick) {
+ return TickType::kLayoutBounds;
+ }
+
+ if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+ *outError = "Ticks in white frame must be black or red";
+ return TickType::kNone;
+ }
+ return TickType::kTick;
}
-enum class TickState {
- kStart,
- kInside1,
- kOutside1
-};
+enum class TickState { kStart, kInside1, kOutside1 };
-static bool getHorizontalTicks(png_bytep row, int width, bool transparent, bool required,
- int32_t* outLeft, int32_t* outRight, const char** outError,
+static bool getHorizontalTicks(png_bytep row, int width, bool transparent,
+ bool required, int32_t* outLeft,
+ int32_t* outRight, const char** outError,
uint8_t* outDivs, bool multipleAllowed) {
- *outLeft = *outRight = -1;
- TickState state = TickState::kStart;
- bool found = false;
+ *outLeft = *outRight = -1;
+ TickState state = TickState::kStart;
+ bool found = false;
- for (int i = 1; i < width - 1; i++) {
- if (tickType(row+i*4, transparent, outError) == TickType::kTick) {
- if (state == TickState::kStart ||
- (state == TickState::kOutside1 && multipleAllowed)) {
- *outLeft = i-1;
- *outRight = width-2;
- found = true;
- if (outDivs != NULL) {
- *outDivs += 2;
- }
- state = TickState::kInside1;
- } else if (state == TickState::kOutside1) {
- *outError = "Can't have more than one marked region along edge";
- *outLeft = i;
- return false;
- }
- } else if (!*outError) {
- if (state == TickState::kInside1) {
- // We're done with this div. Move on to the next.
- *outRight = i-1;
- outRight += 2;
- outLeft += 2;
- state = TickState::kOutside1;
- }
- } else {
- *outLeft = i;
- return false;
+ for (int i = 1; i < width - 1; i++) {
+ if (tickType(row + i * 4, transparent, outError) == TickType::kTick) {
+ if (state == TickState::kStart ||
+ (state == TickState::kOutside1 && multipleAllowed)) {
+ *outLeft = i - 1;
+ *outRight = width - 2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
}
- }
-
- if (required && !found) {
- *outError = "No marked region found along edge";
- *outLeft = -1;
+ state = TickState::kInside1;
+ } else if (state == TickState::kOutside1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outLeft = i;
return false;
+ }
+ } else if (!*outError) {
+ if (state == TickState::kInside1) {
+ // We're done with this div. Move on to the next.
+ *outRight = i - 1;
+ outRight += 2;
+ outLeft += 2;
+ state = TickState::kOutside1;
+ }
+ } else {
+ *outLeft = i;
+ return false;
}
- return true;
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outLeft = -1;
+ return false;
+ }
+ return true;
}
-static bool getVerticalTicks(png_bytepp rows, int offset, int height, bool transparent,
- bool required, int32_t* outTop, int32_t* outBottom,
- const char** outError, uint8_t* outDivs, bool multipleAllowed) {
- *outTop = *outBottom = -1;
- TickState state = TickState::kStart;
- bool found = false;
+static bool getVerticalTicks(png_bytepp rows, int offset, int height,
+ bool transparent, bool required, int32_t* outTop,
+ int32_t* outBottom, const char** outError,
+ uint8_t* outDivs, bool multipleAllowed) {
+ *outTop = *outBottom = -1;
+ TickState state = TickState::kStart;
+ bool found = false;
- for (int i = 1; i < height - 1; i++) {
- if (tickType(rows[i]+offset, transparent, outError) == TickType::kTick) {
- if (state == TickState::kStart ||
- (state == TickState::kOutside1 && multipleAllowed)) {
- *outTop = i-1;
- *outBottom = height-2;
- found = true;
- if (outDivs != NULL) {
- *outDivs += 2;
- }
- state = TickState::kInside1;
- } else if (state == TickState::kOutside1) {
- *outError = "Can't have more than one marked region along edge";
- *outTop = i;
- return false;
- }
- } else if (!*outError) {
- if (state == TickState::kInside1) {
- // We're done with this div. Move on to the next.
- *outBottom = i-1;
- outTop += 2;
- outBottom += 2;
- state = TickState::kOutside1;
- }
- } else {
- *outTop = i;
- return false;
+ for (int i = 1; i < height - 1; i++) {
+ if (tickType(rows[i] + offset, transparent, outError) == TickType::kTick) {
+ if (state == TickState::kStart ||
+ (state == TickState::kOutside1 && multipleAllowed)) {
+ *outTop = i - 1;
+ *outBottom = height - 2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
}
- }
-
- if (required && !found) {
- *outError = "No marked region found along edge";
- *outTop = -1;
+ state = TickState::kInside1;
+ } else if (state == TickState::kOutside1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outTop = i;
return false;
+ }
+ } else if (!*outError) {
+ if (state == TickState::kInside1) {
+ // We're done with this div. Move on to the next.
+ *outBottom = i - 1;
+ outTop += 2;
+ outBottom += 2;
+ state = TickState::kOutside1;
+ }
+ } else {
+ *outTop = i;
+ return false;
}
- return true;
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outTop = -1;
+ return false;
+ }
+ return true;
}
-static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, bool transparent,
- bool /* required */, int32_t* outLeft,
- int32_t* outRight, const char** outError) {
- *outLeft = *outRight = 0;
+static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width,
+ bool transparent,
+ bool /* required */,
+ int32_t* outLeft, int32_t* outRight,
+ const char** outError) {
+ *outLeft = *outRight = 0;
- // Look for left tick
- if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) {
- // Starting with a layout padding tick
- int i = 1;
- while (i < width - 1) {
- (*outLeft)++;
- i++;
- if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) {
- break;
- }
- }
+ // Look for left tick
+ if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) {
+ // Starting with a layout padding tick
+ int i = 1;
+ while (i < width - 1) {
+ (*outLeft)++;
+ i++;
+ if (tickType(row + i * 4, transparent, outError) !=
+ TickType::kLayoutBounds) {
+ break;
+ }
}
+ }
- // Look for right tick
- if (tickType(row + (width - 2) * 4, transparent, outError) == TickType::kLayoutBounds) {
- // Ending with a layout padding tick
- int i = width - 2;
- while (i > 1) {
- (*outRight)++;
- i--;
- if (tickType(row+i*4, transparent, outError) != TickType::kLayoutBounds) {
- break;
- }
- }
+ // Look for right tick
+ if (tickType(row + (width - 2) * 4, transparent, outError) ==
+ TickType::kLayoutBounds) {
+ // Ending with a layout padding tick
+ int i = width - 2;
+ while (i > 1) {
+ (*outRight)++;
+ i--;
+ if (tickType(row + i * 4, transparent, outError) !=
+ TickType::kLayoutBounds) {
+ break;
+ }
}
- return true;
+ }
+ return true;
}
-static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, int height, bool transparent,
- bool /* required */, int32_t* outTop, int32_t* outBottom,
+static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset,
+ int height, bool transparent,
+ bool /* required */, int32_t* outTop,
+ int32_t* outBottom,
const char** outError) {
- *outTop = *outBottom = 0;
+ *outTop = *outBottom = 0;
- // Look for top tick
- if (tickType(rows[1] + offset, transparent, outError) == TickType::kLayoutBounds) {
- // Starting with a layout padding tick
- int i = 1;
- while (i < height - 1) {
- (*outTop)++;
- i++;
- if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
- break;
- }
- }
+ // Look for top tick
+ if (tickType(rows[1] + offset, transparent, outError) ==
+ TickType::kLayoutBounds) {
+ // Starting with a layout padding tick
+ int i = 1;
+ while (i < height - 1) {
+ (*outTop)++;
+ i++;
+ if (tickType(rows[i] + offset, transparent, outError) !=
+ TickType::kLayoutBounds) {
+ break;
+ }
}
+ }
- // Look for bottom tick
- if (tickType(rows[height - 2] + offset, transparent, outError) == TickType::kLayoutBounds) {
- // Ending with a layout padding tick
- int i = height - 2;
- while (i > 1) {
- (*outBottom)++;
- i--;
- if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
- break;
- }
- }
+ // Look for bottom tick
+ if (tickType(rows[height - 2] + offset, transparent, outError) ==
+ TickType::kLayoutBounds) {
+ // Ending with a layout padding tick
+ int i = height - 2;
+ while (i > 1) {
+ (*outBottom)++;
+ i--;
+ if (tickType(rows[i] + offset, transparent, outError) !=
+ TickType::kLayoutBounds) {
+ break;
+ }
}
- return true;
+ }
+ return true;
}
-static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, int endY,
- int dX, int dY, int* outInset) {
- uint8_t maxOpacity = 0;
- int inset = 0;
- *outInset = 0;
- for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
- png_byte* color = rows[y] + x * 4;
- uint8_t opacity = color[3];
- if (opacity > maxOpacity) {
- maxOpacity = opacity;
- *outInset = inset;
- }
- if (opacity == 0xff) return;
+static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX,
+ int endY, int dX, int dY, int* outInset) {
+ uint8_t maxOpacity = 0;
+ int inset = 0;
+ *outInset = 0;
+ for (int x = startX, y = startY; x != endX && y != endY;
+ x += dX, y += dY, inset++) {
+ png_byte* color = rows[y] + x * 4;
+ uint8_t opacity = color[3];
+ if (opacity > maxOpacity) {
+ maxOpacity = opacity;
+ *outInset = inset;
}
+ if (opacity == 0xff) return;
+ }
}
static uint8_t maxAlphaOverRow(png_bytep row, int startX, int endX) {
- uint8_t maxAlpha = 0;
- for (int x = startX; x < endX; x++) {
- uint8_t alpha = (row + x * 4)[3];
- if (alpha > maxAlpha) maxAlpha = alpha;
- }
- return maxAlpha;
+ uint8_t maxAlpha = 0;
+ for (int x = startX; x < endX; x++) {
+ uint8_t alpha = (row + x * 4)[3];
+ if (alpha > maxAlpha) maxAlpha = alpha;
+ }
+ return maxAlpha;
}
-static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, int endY) {
- uint8_t maxAlpha = 0;
- for (int y = startY; y < endY; y++) {
- uint8_t alpha = (rows[y] + offsetX * 4)[3];
- if (alpha > maxAlpha) maxAlpha = alpha;
- }
- return maxAlpha;
+static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY,
+ int endY) {
+ uint8_t maxAlpha = 0;
+ for (int y = startY; y < endY; y++) {
+ uint8_t alpha = (rows[y] + offsetX * 4)[3];
+ if (alpha > maxAlpha) maxAlpha = alpha;
+ }
+ return maxAlpha;
}
static void getOutline(PngInfo* image) {
- int midX = image->width / 2;
- int midY = image->height / 2;
- int endX = image->width - 2;
- int endY = image->height - 2;
+ int midX = image->width / 2;
+ int midY = image->height / 2;
+ int endX = image->width - 2;
+ int endY = image->height - 2;
- // find left and right extent of nine patch content on center row
- if (image->width > 4) {
- findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
- findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0,
- &image->outlineInsetsRight);
- } else {
- image->outlineInsetsLeft = 0;
- image->outlineInsetsRight = 0;
- }
+ // find left and right extent of nine patch content on center row
+ if (image->width > 4) {
+ findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0,
+ &image->outlineInsetsLeft);
+ findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0,
+ &image->outlineInsetsRight);
+ } else {
+ image->outlineInsetsLeft = 0;
+ image->outlineInsetsRight = 0;
+ }
- // find top and bottom extent of nine patch content on center column
- if (image->height > 4) {
- findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
- findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1,
- &image->outlineInsetsBottom);
- } else {
- image->outlineInsetsTop = 0;
- image->outlineInsetsBottom = 0;
- }
+ // find top and bottom extent of nine patch content on center column
+ if (image->height > 4) {
+ findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1,
+ &image->outlineInsetsTop);
+ findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1,
+ &image->outlineInsetsBottom);
+ } else {
+ image->outlineInsetsTop = 0;
+ image->outlineInsetsBottom = 0;
+ }
- int innerStartX = 1 + image->outlineInsetsLeft;
- int innerStartY = 1 + image->outlineInsetsTop;
- int innerEndX = endX - image->outlineInsetsRight;
- int innerEndY = endY - image->outlineInsetsBottom;
- int innerMidX = (innerEndX + innerStartX) / 2;
- int innerMidY = (innerEndY + innerStartY) / 2;
+ int innerStartX = 1 + image->outlineInsetsLeft;
+ int innerStartY = 1 + image->outlineInsetsTop;
+ int innerEndX = endX - image->outlineInsetsRight;
+ int innerEndY = endY - image->outlineInsetsBottom;
+ int innerMidX = (innerEndX + innerStartX) / 2;
+ int innerMidY = (innerEndY + innerStartY) / 2;
- // assuming the image is a round rect, compute the radius by marching
- // diagonally from the top left corner towards the center
- image->outlineAlpha = std::max(
- maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
- maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
+ // assuming the image is a round rect, compute the radius by marching
+ // diagonally from the top left corner towards the center
+ image->outlineAlpha = std::max(
+ maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
+ maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
- int diagonalInset = 0;
- findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
- &diagonalInset);
+ int diagonalInset = 0;
+ findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX,
+ innerMidY, 1, 1, &diagonalInset);
- /* Determine source radius based upon inset:
- * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
- * sqrt(2) * r = sqrt(2) * i + r
- * (sqrt(2) - 1) * r = sqrt(2) * i
- * r = sqrt(2) / (sqrt(2) - 1) * i
- */
- image->outlineRadius = 3.4142f * diagonalInset;
+ /* Determine source radius based upon inset:
+ * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
+ * sqrt(2) * r = sqrt(2) * i + r
+ * (sqrt(2) - 1) * r = sqrt(2) * i
+ * r = sqrt(2) / (sqrt(2) - 1) * i
+ */
+ image->outlineRadius = 3.4142f * diagonalInset;
- if (kDebug) {
- printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
- image->outlineInsetsLeft,
- image->outlineInsetsTop,
- image->outlineInsetsRight,
- image->outlineInsetsBottom,
- image->outlineRadius,
- image->outlineAlpha);
- }
+ if (kDebug) {
+ printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
+ image->outlineInsetsLeft, image->outlineInsetsTop,
+ image->outlineInsetsRight, image->outlineInsetsBottom,
+ image->outlineRadius, image->outlineAlpha);
+ }
}
-static uint32_t getColor(png_bytepp rows, int left, int top, int right, int bottom) {
- png_bytep color = rows[top] + left*4;
+static uint32_t getColor(png_bytepp rows, int left, int top, int right,
+ int bottom) {
+ png_bytep color = rows[top] + left * 4;
- if (left > right || top > bottom) {
- return android::Res_png_9patch::TRANSPARENT_COLOR;
- }
+ if (left > right || top > bottom) {
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
+ }
- while (top <= bottom) {
- for (int i = left; i <= right; i++) {
- png_bytep p = rows[top]+i*4;
- if (color[3] == 0) {
- if (p[3] != 0) {
- return android::Res_png_9patch::NO_COLOR;
- }
- } else if (p[0] != color[0] || p[1] != color[1] ||
- p[2] != color[2] || p[3] != color[3]) {
- return android::Res_png_9patch::NO_COLOR;
- }
+ while (top <= bottom) {
+ for (int i = left; i <= right; i++) {
+ png_bytep p = rows[top] + i * 4;
+ if (color[3] == 0) {
+ if (p[3] != 0) {
+ return android::Res_png_9patch::NO_COLOR;
}
- top++;
+ } else if (p[0] != color[0] || p[1] != color[1] || p[2] != color[2] ||
+ p[3] != color[3]) {
+ return android::Res_png_9patch::NO_COLOR;
+ }
}
+ top++;
+ }
- if (color[3] == 0) {
- return android::Res_png_9patch::TRANSPARENT_COLOR;
- }
- return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
+ if (color[3] == 0) {
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
+ }
+ return (color[3] << 24) | (color[0] << 16) | (color[1] << 8) | color[2];
}
static bool do9Patch(PngInfo* image, std::string* outError) {
- image->is9Patch = true;
+ image->is9Patch = true;
- int W = image->width;
- int H = image->height;
- int i, j;
+ int W = image->width;
+ int H = image->height;
+ int i, j;
- const int maxSizeXDivs = W * sizeof(int32_t);
- const int maxSizeYDivs = H * sizeof(int32_t);
- int32_t* xDivs = image->xDivs = new int32_t[W];
- int32_t* yDivs = image->yDivs = new int32_t[H];
- uint8_t numXDivs = 0;
- uint8_t numYDivs = 0;
+ const int maxSizeXDivs = W * sizeof(int32_t);
+ const int maxSizeYDivs = H * sizeof(int32_t);
+ int32_t* xDivs = image->xDivs = new int32_t[W];
+ int32_t* yDivs = image->yDivs = new int32_t[H];
+ uint8_t numXDivs = 0;
+ uint8_t numYDivs = 0;
- int8_t numColors;
- int numRows;
- int numCols;
- int top;
- int left;
- int right;
- int bottom;
- memset(xDivs, -1, maxSizeXDivs);
- memset(yDivs, -1, maxSizeYDivs);
- image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1;
- image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
- image->layoutBoundsLeft = image->layoutBoundsRight = 0;
- image->layoutBoundsTop = image->layoutBoundsBottom = 0;
+ int8_t numColors;
+ int numRows;
+ int numCols;
+ int top;
+ int left;
+ int right;
+ int bottom;
+ memset(xDivs, -1, maxSizeXDivs);
+ memset(yDivs, -1, maxSizeYDivs);
+ image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1;
+ image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
+ image->layoutBoundsLeft = image->layoutBoundsRight = 0;
+ image->layoutBoundsTop = image->layoutBoundsBottom = 0;
- png_bytep p = image->rows[0];
- bool transparent = p[3] == 0;
- bool hasColor = false;
+ png_bytep p = image->rows[0];
+ bool transparent = p[3] == 0;
+ bool hasColor = false;
- const char* errorMsg = nullptr;
- int errorPixel = -1;
- const char* errorEdge = nullptr;
+ const char* errorMsg = nullptr;
+ int errorPixel = -1;
+ const char* errorEdge = nullptr;
- int colorIndex = 0;
- std::vector<png_bytep> newRows;
+ int colorIndex = 0;
+ std::vector<png_bytep> newRows;
- // Validate size...
- if (W < 3 || H < 3) {
- errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
- goto getout;
+ // Validate size...
+ if (W < 3 || H < 3) {
+ errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
+ goto getout;
+ }
+
+ // Validate frame...
+ if (!transparent &&
+ (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
+ errorMsg = "Must have one-pixel frame that is either transparent or white";
+ goto getout;
+ }
+
+ // Find left and right of sizing areas...
+ if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1],
+ &errorMsg, &numXDivs, true)) {
+ errorPixel = xDivs[0];
+ errorEdge = "top";
+ goto getout;
+ }
+
+ // Find top and bottom of sizing areas...
+ if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0],
+ &yDivs[1], &errorMsg, &numYDivs, true)) {
+ errorPixel = yDivs[0];
+ errorEdge = "left";
+ goto getout;
+ }
+
+ // Copy patch size data into image...
+ image->info9Patch.numXDivs = numXDivs;
+ image->info9Patch.numYDivs = numYDivs;
+
+ // Find left and right of padding area...
+ if (!getHorizontalTicks(image->rows[H - 1], W, transparent, false,
+ &image->info9Patch.paddingLeft,
+ &image->info9Patch.paddingRight, &errorMsg, nullptr,
+ false)) {
+ errorPixel = image->info9Patch.paddingLeft;
+ errorEdge = "bottom";
+ goto getout;
+ }
+
+ // Find top and bottom of padding area...
+ if (!getVerticalTicks(image->rows.data(), (W - 1) * 4, H, transparent, false,
+ &image->info9Patch.paddingTop,
+ &image->info9Patch.paddingBottom, &errorMsg, nullptr,
+ false)) {
+ errorPixel = image->info9Patch.paddingTop;
+ errorEdge = "right";
+ goto getout;
+ }
+
+ // Find left and right of layout padding...
+ getHorizontalLayoutBoundsTicks(image->rows[H - 1], W, transparent, false,
+ &image->layoutBoundsLeft,
+ &image->layoutBoundsRight, &errorMsg);
+
+ getVerticalLayoutBoundsTicks(image->rows.data(), (W - 1) * 4, H, transparent,
+ false, &image->layoutBoundsTop,
+ &image->layoutBoundsBottom, &errorMsg);
+
+ image->haveLayoutBounds =
+ image->layoutBoundsLeft != 0 || image->layoutBoundsRight != 0 ||
+ image->layoutBoundsTop != 0 || image->layoutBoundsBottom != 0;
+
+ if (image->haveLayoutBounds) {
+ if (kDebug) {
+ printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft,
+ image->layoutBoundsTop, image->layoutBoundsRight,
+ image->layoutBoundsBottom);
}
+ }
- // Validate frame...
- if (!transparent &&
- (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
- errorMsg = "Must have one-pixel frame that is either transparent or white";
- goto getout;
- }
+ // use opacity of pixels to estimate the round rect outline
+ getOutline(image);
- // Find left and right of sizing areas...
- if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], &errorMsg, &numXDivs,
- true)) {
- errorPixel = xDivs[0];
- errorEdge = "top";
- goto getout;
- }
+ // If padding is not yet specified, take values from size.
+ if (image->info9Patch.paddingLeft < 0) {
+ image->info9Patch.paddingLeft = xDivs[0];
+ image->info9Patch.paddingRight = W - 2 - xDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
+ }
+ if (image->info9Patch.paddingTop < 0) {
+ image->info9Patch.paddingTop = yDivs[0];
+ image->info9Patch.paddingBottom = H - 2 - yDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
+ }
- // Find top and bottom of sizing areas...
- if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], &yDivs[1],
- &errorMsg, &numYDivs, true)) {
- errorPixel = yDivs[0];
- errorEdge = "left";
- goto getout;
- }
+ /* if (kDebug) {
+ printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
+ xDivs[0], xDivs[1],
+ yDivs[0], yDivs[1]);
+ printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
+ image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
+ image->info9Patch.paddingTop,
+ image->info9Patch.paddingBottom);
+ }*/
- // Copy patch size data into image...
- image->info9Patch.numXDivs = numXDivs;
- image->info9Patch.numYDivs = numYDivs;
+ // Remove frame from image.
+ newRows.resize(H - 2);
+ for (i = 0; i < H - 2; i++) {
+ newRows[i] = image->rows[i + 1];
+ memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
+ }
+ image->rows.swap(newRows);
- // Find left and right of padding area...
- if (!getHorizontalTicks(image->rows[H-1], W, transparent, false,
- &image->info9Patch.paddingLeft, &image->info9Patch.paddingRight,
- &errorMsg, nullptr, false)) {
- errorPixel = image->info9Patch.paddingLeft;
- errorEdge = "bottom";
- goto getout;
- }
+ image->width -= 2;
+ W = image->width;
+ image->height -= 2;
+ H = image->height;
- // Find top and bottom of padding area...
- if (!getVerticalTicks(image->rows.data(), (W-1)*4, H, transparent, false,
- &image->info9Patch.paddingTop, &image->info9Patch.paddingBottom,
- &errorMsg, nullptr, false)) {
- errorPixel = image->info9Patch.paddingTop;
- errorEdge = "right";
- goto getout;
- }
+ // Figure out the number of rows and columns in the N-patch
+ numCols = numXDivs + 1;
+ if (xDivs[0] == 0) { // Column 1 is strechable
+ numCols--;
+ }
+ if (xDivs[numXDivs - 1] == W) {
+ numCols--;
+ }
+ numRows = numYDivs + 1;
+ if (yDivs[0] == 0) { // Row 1 is strechable
+ numRows--;
+ }
+ if (yDivs[numYDivs - 1] == H) {
+ numRows--;
+ }
- // Find left and right of layout padding...
- getHorizontalLayoutBoundsTicks(image->rows[H-1], W, transparent, false,
- &image->layoutBoundsLeft, &image->layoutBoundsRight, &errorMsg);
+ // Make sure the amount of rows and columns will fit in the number of
+ // colors we can use in the 9-patch format.
+ if (numRows * numCols > 0x7F) {
+ errorMsg = "Too many rows and columns in 9-patch perimeter";
+ goto getout;
+ }
- getVerticalLayoutBoundsTicks(image->rows.data(), (W-1)*4, H, transparent, false,
- &image->layoutBoundsTop, &image->layoutBoundsBottom, &errorMsg);
+ numColors = numRows * numCols;
+ image->info9Patch.numColors = numColors;
+ image->colors.resize(numColors);
- image->haveLayoutBounds = image->layoutBoundsLeft != 0
- || image->layoutBoundsRight != 0
- || image->layoutBoundsTop != 0
- || image->layoutBoundsBottom != 0;
+ // Fill in color information for each patch.
- if (image->haveLayoutBounds) {
- if (kDebug) {
- printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
- image->layoutBoundsRight, image->layoutBoundsBottom);
- }
- }
+ uint32_t c;
+ top = 0;
- // use opacity of pixels to estimate the round rect outline
- getOutline(image);
+ // The first row always starts with the top being at y=0 and the bottom
+ // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
+ // the first row is stretchable along the Y axis, otherwise it is fixed.
+ // The last row always ends with the bottom being bitmap.height and the top
+ // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
+ // yDivs[numYDivs-1]. In the former case the last row is stretchable along
+ // the Y axis, otherwise it is fixed.
+ //
+ // The first and last columns are similarly treated with respect to the X
+ // axis.
+ //
+ // The above is to help explain some of the special casing that goes on the
+ // code below.
- // If padding is not yet specified, take values from size.
- if (image->info9Patch.paddingLeft < 0) {
- image->info9Patch.paddingLeft = xDivs[0];
- image->info9Patch.paddingRight = W - 2 - xDivs[1];
+ // The initial yDiv and whether the first row is considered stretchable or
+ // not depends on whether yDiv[0] was zero or not.
+ for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) {
+ if (j == numYDivs) {
+ bottom = H;
} else {
- // Adjust value to be correct!
- image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
+ bottom = yDivs[j];
}
- if (image->info9Patch.paddingTop < 0) {
- image->info9Patch.paddingTop = yDivs[0];
- image->info9Patch.paddingBottom = H - 2 - yDivs[1];
- } else {
- // Adjust value to be correct!
- image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
- }
-
-/* if (kDebug) {
- printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
- xDivs[0], xDivs[1],
- yDivs[0], yDivs[1]);
- printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
- image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
- image->info9Patch.paddingTop, image->info9Patch.paddingBottom);
- }*/
-
- // Remove frame from image.
- newRows.resize(H - 2);
- for (i = 0; i < H - 2; i++) {
- newRows[i] = image->rows[i + 1];
- memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
- }
- image->rows.swap(newRows);
-
- image->width -= 2;
- W = image->width;
- image->height -= 2;
- H = image->height;
-
- // Figure out the number of rows and columns in the N-patch
- numCols = numXDivs + 1;
- if (xDivs[0] == 0) { // Column 1 is strechable
- numCols--;
- }
- if (xDivs[numXDivs - 1] == W) {
- numCols--;
- }
- numRows = numYDivs + 1;
- if (yDivs[0] == 0) { // Row 1 is strechable
- numRows--;
- }
- if (yDivs[numYDivs - 1] == H) {
- numRows--;
- }
-
- // Make sure the amount of rows and columns will fit in the number of
- // colors we can use in the 9-patch format.
- if (numRows * numCols > 0x7F) {
- errorMsg = "Too many rows and columns in 9-patch perimeter";
- goto getout;
- }
-
- numColors = numRows * numCols;
- image->info9Patch.numColors = numColors;
- image->colors.resize(numColors);
-
- // Fill in color information for each patch.
-
- uint32_t c;
- top = 0;
-
- // The first row always starts with the top being at y=0 and the bottom
- // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
- // the first row is stretchable along the Y axis, otherwise it is fixed.
- // The last row always ends with the bottom being bitmap.height and the top
- // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
- // yDivs[numYDivs-1]. In the former case the last row is stretchable along
- // the Y axis, otherwise it is fixed.
- //
- // The first and last columns are similarly treated with respect to the X
- // axis.
- //
- // The above is to help explain some of the special casing that goes on the
- // code below.
-
- // The initial yDiv and whether the first row is considered stretchable or
- // not depends on whether yDiv[0] was zero or not.
- for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) {
- if (j == numYDivs) {
- bottom = H;
- } else {
- bottom = yDivs[j];
+ left = 0;
+ // The initial xDiv and whether the first column is considered
+ // stretchable or not depends on whether xDiv[0] was zero or not.
+ for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) {
+ if (i == numXDivs) {
+ right = W;
+ } else {
+ right = xDivs[i];
+ }
+ c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
+ image->colors[colorIndex++] = c;
+ if (kDebug) {
+ if (c != android::Res_png_9patch::NO_COLOR) {
+ hasColor = true;
}
- left = 0;
- // The initial xDiv and whether the first column is considered
- // stretchable or not depends on whether xDiv[0] was zero or not.
- for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) {
- if (i == numXDivs) {
- right = W;
- } else {
- right = xDivs[i];
- }
- c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
- image->colors[colorIndex++] = c;
- if (kDebug) {
- if (c != android::Res_png_9patch::NO_COLOR) {
- hasColor = true;
- }
- }
- left = right;
- }
- top = bottom;
+ }
+ left = right;
}
+ top = bottom;
+ }
- assert(colorIndex == numColors);
+ assert(colorIndex == numColors);
- if (kDebug && hasColor) {
- for (i = 0; i < numColors; i++) {
- if (i == 0) printf("Colors:\n");
- printf(" #%08x", image->colors[i]);
- if (i == numColors - 1) printf("\n");
- }
+ if (kDebug && hasColor) {
+ for (i = 0; i < numColors; i++) {
+ if (i == 0) printf("Colors:\n");
+ printf(" #%08x", image->colors[i]);
+ if (i == numColors - 1) printf("\n");
}
+ }
getout:
- if (errorMsg) {
- std::stringstream err;
- err << "9-patch malformed: " << errorMsg;
- if (errorEdge) {
- err << "." << std::endl;
- if (errorPixel >= 0) {
- err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge";
- } else {
- err << "Found along " << errorEdge << " edge";
- }
- }
- *outError = err.str();
- return false;
+ if (errorMsg) {
+ std::stringstream err;
+ err << "9-patch malformed: " << errorMsg;
+ if (errorEdge) {
+ err << "." << std::endl;
+ if (errorPixel >= 0) {
+ err << "Found at pixel #" << errorPixel << " along " << errorEdge
+ << " edge";
+ } else {
+ err << "Found along " << errorEdge << " edge";
+ }
}
- return true;
+ *outError = err.str();
+ return false;
+ }
+ return true;
}
+bool Png::process(const Source& source, std::istream* input,
+ BigBuffer* outBuffer, const PngOptions& options) {
+ png_byte signature[kPngSignatureSize];
-bool Png::process(const Source& source, std::istream* input, BigBuffer* outBuffer,
- const PngOptions& options) {
- png_byte signature[kPngSignatureSize];
+ // Read the PNG signature first.
+ if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
+ mDiag->error(DiagMessage() << strerror(errno));
+ return false;
+ }
- // Read the PNG signature first.
- if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
- mDiag->error(DiagMessage() << strerror(errno));
- return false;
+ // If the PNG signature doesn't match, bail early.
+ if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+ mDiag->error(DiagMessage() << "not a valid png file");
+ return false;
+ }
+
+ bool result = false;
+ png_structp readPtr = nullptr;
+ png_infop infoPtr = nullptr;
+ png_structp writePtr = nullptr;
+ png_infop writeInfoPtr = nullptr;
+ PngInfo pngInfo = {};
+
+ readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+ if (!readPtr) {
+ mDiag->error(DiagMessage() << "failed to allocate read ptr");
+ goto bail;
+ }
+
+ infoPtr = png_create_info_struct(readPtr);
+ if (!infoPtr) {
+ mDiag->error(DiagMessage() << "failed to allocate info ptr");
+ goto bail;
+ }
+
+ png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr,
+ logWarning);
+
+ // Set the read function to read from std::istream.
+ png_set_read_fn(readPtr, (png_voidp)input, readDataFromStream);
+
+ if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) {
+ goto bail;
+ }
+
+ if (util::stringEndsWith(source.path, ".9.png")) {
+ std::string errorMsg;
+ if (!do9Patch(&pngInfo, &errorMsg)) {
+ mDiag->error(DiagMessage() << errorMsg);
+ goto bail;
}
+ }
- // If the PNG signature doesn't match, bail early.
- if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
- mDiag->error(DiagMessage() << "not a valid png file");
- return false;
- }
+ writePtr =
+ png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+ if (!writePtr) {
+ mDiag->error(DiagMessage() << "failed to allocate write ptr");
+ goto bail;
+ }
- bool result = false;
- png_structp readPtr = nullptr;
- png_infop infoPtr = nullptr;
- png_structp writePtr = nullptr;
- png_infop writeInfoPtr = nullptr;
- PngInfo pngInfo = {};
+ writeInfoPtr = png_create_info_struct(writePtr);
+ if (!writeInfoPtr) {
+ mDiag->error(DiagMessage() << "failed to allocate write info ptr");
+ goto bail;
+ }
- readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
- if (!readPtr) {
- mDiag->error(DiagMessage() << "failed to allocate read ptr");
- goto bail;
- }
+ png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
- infoPtr = png_create_info_struct(readPtr);
- if (!infoPtr) {
- mDiag->error(DiagMessage() << "failed to allocate info ptr");
- goto bail;
- }
+ // Set the write function to write to std::ostream.
+ png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream,
+ flushDataToStream);
- png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning);
+ if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo,
+ options.grayScaleTolerance)) {
+ goto bail;
+ }
- // Set the read function to read from std::istream.
- png_set_read_fn(readPtr, (png_voidp) input, readDataFromStream);
-
- if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) {
- goto bail;
- }
-
- if (util::stringEndsWith(source.path, ".9.png")) {
- std::string errorMsg;
- if (!do9Patch(&pngInfo, &errorMsg)) {
- mDiag->error(DiagMessage() << errorMsg);
- goto bail;
- }
- }
-
- writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
- if (!writePtr) {
- mDiag->error(DiagMessage() << "failed to allocate write ptr");
- goto bail;
- }
-
- writeInfoPtr = png_create_info_struct(writePtr);
- if (!writeInfoPtr) {
- mDiag->error(DiagMessage() << "failed to allocate write info ptr");
- goto bail;
- }
-
- png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
-
- // Set the write function to write to std::ostream.
- png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream);
-
- if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance)) {
- goto bail;
- }
-
- result = true;
+ result = true;
bail:
- if (readPtr) {
- png_destroy_read_struct(&readPtr, &infoPtr, nullptr);
- }
+ if (readPtr) {
+ png_destroy_read_struct(&readPtr, &infoPtr, nullptr);
+ }
- if (writePtr) {
- png_destroy_write_struct(&writePtr, &writeInfoPtr);
- }
- return result;
+ if (writePtr) {
+ png_destroy_write_struct(&writePtr, &writeInfoPtr);
+ }
+ return result;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h
index 4a15d95..f9038af 100644
--- a/tools/aapt2/compile/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -31,51 +31,48 @@
namespace aapt {
struct PngOptions {
- int grayScaleTolerance = 0;
+ int grayScaleTolerance = 0;
};
class Png {
-public:
- explicit Png(IDiagnostics* diag) : mDiag(diag) {
- }
+ public:
+ explicit Png(IDiagnostics* diag) : mDiag(diag) {}
- bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
- const PngOptions& options);
+ bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+ const PngOptions& options);
-private:
- IDiagnostics* mDiag;
+ private:
+ IDiagnostics* mDiag;
- DISALLOW_COPY_AND_ASSIGN(Png);
+ DISALLOW_COPY_AND_ASSIGN(Png);
};
/**
* An InputStream that filters out unimportant PNG chunks.
*/
class PngChunkFilter : public io::InputStream {
-public:
- explicit PngChunkFilter(const StringPiece& data);
+ public:
+ explicit PngChunkFilter(const StringPiece& data);
- bool Next(const void** buffer, int* len) override;
- void BackUp(int count) override;
- bool Skip(int count) override;
+ bool Next(const void** buffer, int* len) override;
+ void BackUp(int count) override;
+ bool Skip(int count) override;
- int64_t ByteCount() const override {
- return static_cast<int64_t>(mWindowStart);
- }
+ int64_t ByteCount() const override {
+ return static_cast<int64_t>(mWindowStart);
+ }
- bool HadError() const override {
- return mError;
- }
+ bool HadError() const override { return mError; }
-private:
- bool consumeWindow(const void** buffer, int* len);
+ private:
+ bool consumeWindow(const void** buffer, int* len);
- StringPiece mData;
- size_t mWindowStart = 0;
- size_t mWindowEnd = 0;
- bool mError = false;
+ StringPiece mData;
+ size_t mWindowStart = 0;
+ size_t mWindowEnd = 0;
+ bool mError = false;
- DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
+ DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
};
/**
@@ -84,11 +81,13 @@
std::unique_ptr<Image> readPng(IAaptContext* context, io::InputStream* in);
/**
- * Writes the RGBA Image, with optional 9-patch meta-data, into the OutputStream as a PNG.
+ * Writes the RGBA Image, with optional 9-patch meta-data, into the OutputStream
+ * as a PNG.
*/
-bool writePng(IAaptContext* context, const Image* image, const NinePatch* ninePatch,
- io::OutputStream* out, const PngOptions& options);
+bool writePng(IAaptContext* context, const Image* image,
+ const NinePatch* ninePatch, io::OutputStream* out,
+ const PngOptions& options);
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_PNG_H
+#endif // AAPT_PNG_H
diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
index 70a881f..fb7fe92 100644
--- a/tools/aapt2/compile/PngChunkFilter.cpp
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -25,149 +25,149 @@
// Useful helper function that encodes individual bytes into a uint32
// at compile time.
constexpr uint32_t u32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
- return (((uint32_t) a) << 24)
- | (((uint32_t) b) << 16)
- | (((uint32_t) c) << 8)
- | ((uint32_t) d);
+ return (((uint32_t)a) << 24) | (((uint32_t)b) << 16) | (((uint32_t)c) << 8) |
+ ((uint32_t)d);
}
// Whitelist of PNG chunk types that we want to keep in the resulting PNG.
enum PngChunkTypes {
- kPngChunkIHDR = u32(73, 72, 68, 82),
- kPngChunkIDAT = u32(73, 68, 65, 84),
- kPngChunkIEND = u32(73, 69, 78, 68),
- kPngChunkPLTE = u32(80, 76, 84, 69),
- kPngChunktRNS = u32(116, 82, 78, 83),
- kPngChunksRGB = u32(115, 82, 71, 66),
+ kPngChunkIHDR = u32(73, 72, 68, 82),
+ kPngChunkIDAT = u32(73, 68, 65, 84),
+ kPngChunkIEND = u32(73, 69, 78, 68),
+ kPngChunkPLTE = u32(80, 76, 84, 69),
+ kPngChunktRNS = u32(116, 82, 78, 83),
+ kPngChunksRGB = u32(115, 82, 71, 66),
};
static uint32_t peek32LE(const char* data) {
- uint32_t word = ((uint32_t) data[0]) & 0x000000ff;
- word <<= 8;
- word |= ((uint32_t) data[1]) & 0x000000ff;
- word <<= 8;
- word |= ((uint32_t) data[2]) & 0x000000ff;
- word <<= 8;
- word |= ((uint32_t) data[3]) & 0x000000ff;
- return word;
+ uint32_t word = ((uint32_t)data[0]) & 0x000000ff;
+ word <<= 8;
+ word |= ((uint32_t)data[1]) & 0x000000ff;
+ word <<= 8;
+ word |= ((uint32_t)data[2]) & 0x000000ff;
+ word <<= 8;
+ word |= ((uint32_t)data[3]) & 0x000000ff;
+ return word;
}
static bool isPngChunkWhitelisted(uint32_t type) {
- switch (type) {
+ switch (type) {
case kPngChunkIHDR:
case kPngChunkIDAT:
case kPngChunkIEND:
case kPngChunkPLTE:
case kPngChunktRNS:
case kPngChunksRGB:
- return true;
+ return true;
default:
- return false;
- }
+ return false;
+ }
}
PngChunkFilter::PngChunkFilter(const StringPiece& data) : mData(data) {
- if (util::stringStartsWith(mData, kPngSignature)) {
- mWindowStart = 0;
- mWindowEnd = strlen(kPngSignature);
- } else {
- mError = true;
- }
+ if (util::stringStartsWith(mData, kPngSignature)) {
+ mWindowStart = 0;
+ mWindowEnd = strlen(kPngSignature);
+ } else {
+ mError = true;
+ }
}
bool PngChunkFilter::consumeWindow(const void** buffer, int* len) {
- if (mWindowStart != mWindowEnd) {
- // We have bytes to give from our window.
- const int bytesRead = (int) (mWindowEnd - mWindowStart);
- *buffer = mData.data() + mWindowStart;
- *len = bytesRead;
- mWindowStart = mWindowEnd;
- return true;
- }
- return false;
+ if (mWindowStart != mWindowEnd) {
+ // We have bytes to give from our window.
+ const int bytesRead = (int)(mWindowEnd - mWindowStart);
+ *buffer = mData.data() + mWindowStart;
+ *len = bytesRead;
+ mWindowStart = mWindowEnd;
+ return true;
+ }
+ return false;
}
bool PngChunkFilter::Next(const void** buffer, int* len) {
- if (mError) {
- return false;
- }
-
- // In case BackUp was called, we must consume the window.
- if (consumeWindow(buffer, len)) {
- return true;
- }
-
- // Advance the window as far as possible (until we meet a chunk that
- // we want to strip).
- while (mWindowEnd < mData.size()) {
- // Chunk length (4 bytes) + type (4 bytes) + crc32 (4 bytes) = 12 bytes.
- const size_t kMinChunkHeaderSize = 3 * sizeof(uint32_t);
-
- // Is there enough room for a chunk header?
- if (mData.size() - mWindowStart < kMinChunkHeaderSize) {
- mError = true;
- return false;
- }
-
- // Verify the chunk length.
- const uint32_t chunkLen = peek32LE(mData.data() + mWindowEnd);
- if (((uint64_t) chunkLen) + ((uint64_t) mWindowEnd) + sizeof(uint32_t) > mData.size()) {
- // Overflow.
- mError = true;
- return false;
- }
-
- // Do we strip this chunk?
- const uint32_t chunkType = peek32LE(mData.data() + mWindowEnd + sizeof(uint32_t));
- if (isPngChunkWhitelisted(chunkType)) {
- // Advance the window to include this chunk.
- mWindowEnd += kMinChunkHeaderSize + chunkLen;
- } else {
- // We want to strip this chunk. If we accumulated a window,
- // we must return the window now.
- if (mWindowStart != mWindowEnd) {
- break;
- }
-
- // The window is empty, so we can advance past this chunk
- // and keep looking for the next good chunk,
- mWindowEnd += kMinChunkHeaderSize + chunkLen;
- mWindowStart = mWindowEnd;
- }
- }
-
- if (consumeWindow(buffer, len)) {
- return true;
- }
+ if (mError) {
return false;
+ }
+
+ // In case BackUp was called, we must consume the window.
+ if (consumeWindow(buffer, len)) {
+ return true;
+ }
+
+ // Advance the window as far as possible (until we meet a chunk that
+ // we want to strip).
+ while (mWindowEnd < mData.size()) {
+ // Chunk length (4 bytes) + type (4 bytes) + crc32 (4 bytes) = 12 bytes.
+ const size_t kMinChunkHeaderSize = 3 * sizeof(uint32_t);
+
+ // Is there enough room for a chunk header?
+ if (mData.size() - mWindowStart < kMinChunkHeaderSize) {
+ mError = true;
+ return false;
+ }
+
+ // Verify the chunk length.
+ const uint32_t chunkLen = peek32LE(mData.data() + mWindowEnd);
+ if (((uint64_t)chunkLen) + ((uint64_t)mWindowEnd) + sizeof(uint32_t) >
+ mData.size()) {
+ // Overflow.
+ mError = true;
+ return false;
+ }
+
+ // Do we strip this chunk?
+ const uint32_t chunkType =
+ peek32LE(mData.data() + mWindowEnd + sizeof(uint32_t));
+ if (isPngChunkWhitelisted(chunkType)) {
+ // Advance the window to include this chunk.
+ mWindowEnd += kMinChunkHeaderSize + chunkLen;
+ } else {
+ // We want to strip this chunk. If we accumulated a window,
+ // we must return the window now.
+ if (mWindowStart != mWindowEnd) {
+ break;
+ }
+
+ // The window is empty, so we can advance past this chunk
+ // and keep looking for the next good chunk,
+ mWindowEnd += kMinChunkHeaderSize + chunkLen;
+ mWindowStart = mWindowEnd;
+ }
+ }
+
+ if (consumeWindow(buffer, len)) {
+ return true;
+ }
+ return false;
}
void PngChunkFilter::BackUp(int count) {
- if (mError) {
- return;
- }
- mWindowStart -= count;
+ if (mError) {
+ return;
+ }
+ mWindowStart -= count;
}
bool PngChunkFilter::Skip(int count) {
- if (mError) {
- return false;
- }
+ if (mError) {
+ return false;
+ }
- const void* buffer;
- int len;
- while (count > 0) {
- if (!Next(&buffer, &len)) {
- return false;
- }
- if (len > count) {
- BackUp(len - count);
- count = 0;
- } else {
- count -= len;
- }
+ const void* buffer;
+ int len;
+ while (count > 0) {
+ if (!Next(&buffer, &len)) {
+ return false;
}
- return true;
+ if (len > count) {
+ BackUp(len - count);
+ count = 0;
+ } else {
+ count -= len;
+ }
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/PngCrunch.cpp b/tools/aapt2/compile/PngCrunch.cpp
index a2e3f4f..4a74f7af7 100644
--- a/tools/aapt2/compile/PngCrunch.cpp
+++ b/tools/aapt2/compile/PngCrunch.cpp
@@ -16,13 +16,13 @@
#include "compile/Png.h"
-#include <algorithm>
#include <android-base/errors.h>
#include <android-base/macros.h>
#include <png.h>
+#include <zlib.h>
+#include <algorithm>
#include <unordered_map>
#include <unordered_set>
-#include <zlib.h>
namespace aapt {
@@ -33,250 +33,261 @@
* Custom deleter that destroys libpng read and info structs.
*/
class PngReadStructDeleter {
-public:
- explicit PngReadStructDeleter(png_structp readPtr, png_infop infoPtr) :
- mReadPtr(readPtr), mInfoPtr(infoPtr) {
- }
+ public:
+ explicit PngReadStructDeleter(png_structp readPtr, png_infop infoPtr)
+ : mReadPtr(readPtr), mInfoPtr(infoPtr) {}
- ~PngReadStructDeleter() {
- png_destroy_read_struct(&mReadPtr, &mInfoPtr, nullptr);
- }
+ ~PngReadStructDeleter() {
+ png_destroy_read_struct(&mReadPtr, &mInfoPtr, nullptr);
+ }
-private:
- png_structp mReadPtr;
- png_infop mInfoPtr;
+ private:
+ png_structp mReadPtr;
+ png_infop mInfoPtr;
- DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
+ DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
};
/**
* Custom deleter that destroys libpng write and info structs.
*/
class PngWriteStructDeleter {
-public:
- explicit PngWriteStructDeleter(png_structp writePtr, png_infop infoPtr) :
- mWritePtr(writePtr), mInfoPtr(infoPtr) {
- }
+ public:
+ explicit PngWriteStructDeleter(png_structp writePtr, png_infop infoPtr)
+ : mWritePtr(writePtr), mInfoPtr(infoPtr) {}
- ~PngWriteStructDeleter() {
- png_destroy_write_struct(&mWritePtr, &mInfoPtr);
- }
+ ~PngWriteStructDeleter() { png_destroy_write_struct(&mWritePtr, &mInfoPtr); }
-private:
- png_structp mWritePtr;
- png_infop mInfoPtr;
+ private:
+ png_structp mWritePtr;
+ png_infop mInfoPtr;
- DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter);
+ DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter);
};
// Custom warning logging method that uses IDiagnostics.
static void logWarning(png_structp pngPtr, png_const_charp warningMsg) {
- IDiagnostics* diag = (IDiagnostics*) png_get_error_ptr(pngPtr);
- diag->warn(DiagMessage() << warningMsg);
+ IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(pngPtr);
+ diag->warn(DiagMessage() << warningMsg);
}
// Custom error logging method that uses IDiagnostics.
static void logError(png_structp pngPtr, png_const_charp errorMsg) {
- IDiagnostics* diag = (IDiagnostics*) png_get_error_ptr(pngPtr);
- diag->error(DiagMessage() << errorMsg);
+ IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(pngPtr);
+ diag->error(DiagMessage() << errorMsg);
}
-static void readDataFromStream(png_structp pngPtr, png_bytep buffer, png_size_t len) {
- io::InputStream* in = (io::InputStream*) png_get_io_ptr(pngPtr);
+static void readDataFromStream(png_structp pngPtr, png_bytep buffer,
+ png_size_t len) {
+ io::InputStream* in = (io::InputStream*)png_get_io_ptr(pngPtr);
- const void* inBuffer;
- int inLen;
- if (!in->Next(&inBuffer, &inLen)) {
- if (in->HadError()) {
- std::string err = in->GetError();
- png_error(pngPtr, err.c_str());
- }
- return;
+ const void* inBuffer;
+ int inLen;
+ if (!in->Next(&inBuffer, &inLen)) {
+ if (in->HadError()) {
+ std::string err = in->GetError();
+ png_error(pngPtr, err.c_str());
}
+ return;
+ }
- const size_t bytesRead = std::min(static_cast<size_t>(inLen), len);
- memcpy(buffer, inBuffer, bytesRead);
- if (bytesRead != static_cast<size_t>(inLen)) {
- in->BackUp(inLen - static_cast<int>(bytesRead));
- }
+ const size_t bytesRead = std::min(static_cast<size_t>(inLen), len);
+ memcpy(buffer, inBuffer, bytesRead);
+ if (bytesRead != static_cast<size_t>(inLen)) {
+ in->BackUp(inLen - static_cast<int>(bytesRead));
+ }
}
-static void writeDataToStream(png_structp pngPtr, png_bytep buffer, png_size_t len) {
- io::OutputStream* out = (io::OutputStream*) png_get_io_ptr(pngPtr);
+static void writeDataToStream(png_structp pngPtr, png_bytep buffer,
+ png_size_t len) {
+ io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(pngPtr);
- void* outBuffer;
- int outLen;
- while (len > 0) {
- if (!out->Next(&outBuffer, &outLen)) {
- if (out->HadError()) {
- std::string err = out->GetError();
- png_error(pngPtr, err.c_str());
- }
- return;
- }
-
- const size_t bytesWritten = std::min(static_cast<size_t>(outLen), len);
- memcpy(outBuffer, buffer, bytesWritten);
-
- // Advance the input buffer.
- buffer += bytesWritten;
- len -= bytesWritten;
-
- // Advance the output buffer.
- outLen -= static_cast<int>(bytesWritten);
+ void* outBuffer;
+ int outLen;
+ while (len > 0) {
+ if (!out->Next(&outBuffer, &outLen)) {
+ if (out->HadError()) {
+ std::string err = out->GetError();
+ png_error(pngPtr, err.c_str());
+ }
+ return;
}
- // If the entire output buffer wasn't used, backup.
- if (outLen > 0) {
- out->BackUp(outLen);
- }
+ const size_t bytesWritten = std::min(static_cast<size_t>(outLen), len);
+ memcpy(outBuffer, buffer, bytesWritten);
+
+ // Advance the input buffer.
+ buffer += bytesWritten;
+ len -= bytesWritten;
+
+ // Advance the output buffer.
+ outLen -= static_cast<int>(bytesWritten);
+ }
+
+ // If the entire output buffer wasn't used, backup.
+ if (outLen > 0) {
+ out->BackUp(outLen);
+ }
}
std::unique_ptr<Image> readPng(IAaptContext* context, io::InputStream* in) {
- // Read the first 8 bytes of the file looking for the PNG signature.
- // Bail early if it does not match.
- const png_byte* signature;
- int bufferSize;
- if (!in->Next((const void**) &signature, &bufferSize)) {
- context->getDiagnostics()->error(DiagMessage()
- << android::base::SystemErrorCodeToString(errno));
- return {};
- }
+ // Read the first 8 bytes of the file looking for the PNG signature.
+ // Bail early if it does not match.
+ const png_byte* signature;
+ int bufferSize;
+ if (!in->Next((const void**)&signature, &bufferSize)) {
+ context->getDiagnostics()->error(
+ DiagMessage() << android::base::SystemErrorCodeToString(errno));
+ return {};
+ }
- if (static_cast<size_t>(bufferSize) < kPngSignatureSize
- || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
- context->getDiagnostics()->error(DiagMessage()
- << "file signature does not match PNG signature");
- return {};
- }
+ if (static_cast<size_t>(bufferSize) < kPngSignatureSize ||
+ png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+ context->getDiagnostics()->error(
+ DiagMessage() << "file signature does not match PNG signature");
+ return {};
+ }
- // Start at the beginning of the first chunk.
- in->BackUp(bufferSize - static_cast<int>(kPngSignatureSize));
+ // Start at the beginning of the first chunk.
+ in->BackUp(bufferSize - static_cast<int>(kPngSignatureSize));
- // Create and initialize the png_struct with the default error and warning handlers.
- // The header version is also passed in to ensure that this was built against the same
- // version of libpng.
- png_structp readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
- if (readPtr == nullptr) {
- context->getDiagnostics()->error(DiagMessage()
- << "failed to create libpng read png_struct");
- return {};
- }
+ // Create and initialize the png_struct with the default error and warning
+ // handlers.
+ // The header version is also passed in to ensure that this was built against
+ // the same
+ // version of libpng.
+ png_structp readPtr =
+ png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (readPtr == nullptr) {
+ context->getDiagnostics()->error(
+ DiagMessage() << "failed to create libpng read png_struct");
+ return {};
+ }
- // Create and initialize the memory for image header and data.
- png_infop infoPtr = png_create_info_struct(readPtr);
- if (infoPtr == nullptr) {
- context->getDiagnostics()->error(DiagMessage() << "failed to create libpng read png_info");
- png_destroy_read_struct(&readPtr, nullptr, nullptr);
- return {};
- }
+ // Create and initialize the memory for image header and data.
+ png_infop infoPtr = png_create_info_struct(readPtr);
+ if (infoPtr == nullptr) {
+ context->getDiagnostics()->error(
+ DiagMessage() << "failed to create libpng read png_info");
+ png_destroy_read_struct(&readPtr, nullptr, nullptr);
+ return {};
+ }
- // Automatically release PNG resources at end of scope.
- PngReadStructDeleter pngReadDeleter(readPtr, infoPtr);
+ // Automatically release PNG resources at end of scope.
+ PngReadStructDeleter pngReadDeleter(readPtr, infoPtr);
- // libpng uses longjmp to jump to an error handling routine.
- // setjmp will only return true if it was jumped to, aka there was
- // an error.
- if (setjmp(png_jmpbuf(readPtr))) {
- return {};
- }
+ // libpng uses longjmp to jump to an error handling routine.
+ // setjmp will only return true if it was jumped to, aka there was
+ // an error.
+ if (setjmp(png_jmpbuf(readPtr))) {
+ return {};
+ }
- // Handle warnings ourselves via IDiagnostics.
- png_set_error_fn(readPtr, (png_voidp) context->getDiagnostics(), logError, logWarning);
+ // Handle warnings ourselves via IDiagnostics.
+ png_set_error_fn(readPtr, (png_voidp)context->getDiagnostics(), logError,
+ logWarning);
- // Set up the read functions which read from our custom data sources.
- png_set_read_fn(readPtr, (png_voidp) in, readDataFromStream);
+ // Set up the read functions which read from our custom data sources.
+ png_set_read_fn(readPtr, (png_voidp)in, readDataFromStream);
- // Skip the signature that we already read.
- png_set_sig_bytes(readPtr, kPngSignatureSize);
+ // Skip the signature that we already read.
+ png_set_sig_bytes(readPtr, kPngSignatureSize);
- // Read the chunk headers.
- png_read_info(readPtr, infoPtr);
+ // Read the chunk headers.
+ png_read_info(readPtr, infoPtr);
- // Extract image meta-data from the various chunk headers.
- uint32_t width, height;
- int bitDepth, colorType, interlaceMethod, compressionMethod, filterMethod;
- png_get_IHDR(readPtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceMethod,
- &compressionMethod, &filterMethod);
+ // Extract image meta-data from the various chunk headers.
+ uint32_t width, height;
+ int bitDepth, colorType, interlaceMethod, compressionMethod, filterMethod;
+ png_get_IHDR(readPtr, infoPtr, &width, &height, &bitDepth, &colorType,
+ &interlaceMethod, &compressionMethod, &filterMethod);
- // When the image is read, expand it so that it is in RGBA 8888 format
- // so that image handling is uniform.
+ // When the image is read, expand it so that it is in RGBA 8888 format
+ // so that image handling is uniform.
- if (colorType == PNG_COLOR_TYPE_PALETTE) {
- png_set_palette_to_rgb(readPtr);
- }
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ png_set_palette_to_rgb(readPtr);
+ }
- if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
- png_set_expand_gray_1_2_4_to_8(readPtr);
- }
+ if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
+ png_set_expand_gray_1_2_4_to_8(readPtr);
+ }
- if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
- png_set_tRNS_to_alpha(readPtr);
- }
+ if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
+ png_set_tRNS_to_alpha(readPtr);
+ }
- if (bitDepth == 16) {
- png_set_strip_16(readPtr);
- }
+ if (bitDepth == 16) {
+ png_set_strip_16(readPtr);
+ }
- if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
- png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
- }
+ if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
+ png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
+ }
- if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
- png_set_gray_to_rgb(readPtr);
- }
+ if (colorType == PNG_COLOR_TYPE_GRAY ||
+ colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(readPtr);
+ }
- if (interlaceMethod != PNG_INTERLACE_NONE) {
- png_set_interlace_handling(readPtr);
- }
+ if (interlaceMethod != PNG_INTERLACE_NONE) {
+ png_set_interlace_handling(readPtr);
+ }
- // Once all the options for reading have been set, we need to flush
- // them to libpng.
- png_read_update_info(readPtr, infoPtr);
+ // Once all the options for reading have been set, we need to flush
+ // them to libpng.
+ png_read_update_info(readPtr, infoPtr);
- // 9-patch uses int32_t to index images, so we cap the image dimensions to something
- // that can always be represented by 9-patch.
- if (width > std::numeric_limits<int32_t>::max() ||
- height > std::numeric_limits<int32_t>::max()) {
- context->getDiagnostics()->error(DiagMessage() << "PNG image dimensions are too large: "
- << width << "x" << height);
- return {};
- }
+ // 9-patch uses int32_t to index images, so we cap the image dimensions to
+ // something
+ // that can always be represented by 9-patch.
+ if (width > std::numeric_limits<int32_t>::max() ||
+ height > std::numeric_limits<int32_t>::max()) {
+ context->getDiagnostics()->error(DiagMessage()
+ << "PNG image dimensions are too large: "
+ << width << "x" << height);
+ return {};
+ }
- std::unique_ptr<Image> outputImage = util::make_unique<Image>();
- outputImage->width = static_cast<int32_t>(width);
- outputImage->height = static_cast<int32_t>(height);
+ std::unique_ptr<Image> outputImage = util::make_unique<Image>();
+ outputImage->width = static_cast<int32_t>(width);
+ outputImage->height = static_cast<int32_t>(height);
- const size_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
- assert(rowBytes == 4 * width); // RGBA
+ const size_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
+ assert(rowBytes == 4 * width); // RGBA
- // Allocate one large block to hold the image.
- outputImage->data = std::unique_ptr<uint8_t[]>(new uint8_t[height * rowBytes]);
+ // Allocate one large block to hold the image.
+ outputImage->data =
+ std::unique_ptr<uint8_t[]>(new uint8_t[height * rowBytes]);
- // Create an array of rows that index into the data block.
- outputImage->rows = std::unique_ptr<uint8_t*[]>(new uint8_t*[height]);
- for (uint32_t h = 0; h < height; h++) {
- outputImage->rows[h] = outputImage->data.get() + (h * rowBytes);
- }
+ // Create an array of rows that index into the data block.
+ outputImage->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]);
+ for (uint32_t h = 0; h < height; h++) {
+ outputImage->rows[h] = outputImage->data.get() + (h * rowBytes);
+ }
- // Actually read the image pixels.
- png_read_image(readPtr, outputImage->rows.get());
+ // Actually read the image pixels.
+ png_read_image(readPtr, outputImage->rows.get());
- // Finish reading. This will read any other chunks after the image data.
- png_read_end(readPtr, infoPtr);
+ // Finish reading. This will read any other chunks after the image data.
+ png_read_end(readPtr, infoPtr);
- return outputImage;
+ return outputImage;
}
/**
- * Experimentally chosen constant to be added to the overhead of using color type
- * PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette chunk.
- * Without this, many small PNGs encoded with palettes are larger after compression than
+ * Experimentally chosen constant to be added to the overhead of using color
+ * type
+ * PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette
+ * chunk.
+ * Without this, many small PNGs encoded with palettes are larger after
+ * compression than
* the same PNGs encoded as RGBA.
*/
constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u;
-// Pick a color type by which to encode the image, based on which color type will take
+// Pick a color type by which to encode the image, based on which color type
+// will take
// the least amount of disk space.
//
// 9-patch images traditionally have not been encoded with palettes.
@@ -298,427 +309,450 @@
// - Grayscale + cheap alpha
// - Grayscale + alpha
//
-static int pickColorType(int32_t width, int32_t height,
- bool grayScale, bool convertibleToGrayScale, bool hasNinePatch,
+static int pickColorType(int32_t width, int32_t height, bool grayScale,
+ bool convertibleToGrayScale, bool hasNinePatch,
size_t colorPaletteSize, size_t alphaPaletteSize) {
- const size_t paletteChunkSize = 16 + colorPaletteSize * 3;
- const size_t alphaChunkSize = 16 + alphaPaletteSize;
- const size_t colorAlphaDataChunkSize = 16 + 4 * width * height;
- const size_t colorDataChunkSize = 16 + 3 * width * height;
- const size_t grayScaleAlphaDataChunkSize = 16 + 2 * width * height;
- const size_t paletteDataChunkSize = 16 + width * height;
+ const size_t paletteChunkSize = 16 + colorPaletteSize * 3;
+ const size_t alphaChunkSize = 16 + alphaPaletteSize;
+ const size_t colorAlphaDataChunkSize = 16 + 4 * width * height;
+ const size_t colorDataChunkSize = 16 + 3 * width * height;
+ const size_t grayScaleAlphaDataChunkSize = 16 + 2 * width * height;
+ const size_t paletteDataChunkSize = 16 + width * height;
- if (grayScale) {
- if (alphaPaletteSize == 0) {
- // This is the smallest the data can be.
- return PNG_COLOR_TYPE_GRAY;
- } else if (colorPaletteSize <= 256 && !hasNinePatch) {
- // This grayscale has alpha and can fit within a palette.
- // See if it is worth fitting into a palette.
- const size_t paletteThreshold = paletteChunkSize + alphaChunkSize +
- paletteDataChunkSize + kPaletteOverheadConstant;
- if (grayScaleAlphaDataChunkSize > paletteThreshold) {
- return PNG_COLOR_TYPE_PALETTE;
- }
- }
- return PNG_COLOR_TYPE_GRAY_ALPHA;
- }
-
-
- if (colorPaletteSize <= 256 && !hasNinePatch) {
- // This image can fit inside a palette. Let's see if it is worth it.
- size_t totalSizeWithPalette = paletteDataChunkSize + paletteChunkSize;
- size_t totalSizeWithoutPalette = colorDataChunkSize;
- if (alphaPaletteSize > 0) {
- totalSizeWithPalette += alphaPaletteSize;
- totalSizeWithoutPalette = colorAlphaDataChunkSize;
- }
-
- if (totalSizeWithoutPalette > totalSizeWithPalette + kPaletteOverheadConstant) {
- return PNG_COLOR_TYPE_PALETTE;
- }
- }
-
- if (convertibleToGrayScale) {
- if (alphaPaletteSize == 0) {
- return PNG_COLOR_TYPE_GRAY;
- } else {
- return PNG_COLOR_TYPE_GRAY_ALPHA;
- }
- }
-
+ if (grayScale) {
if (alphaPaletteSize == 0) {
- return PNG_COLOR_TYPE_RGB;
+ // This is the smallest the data can be.
+ return PNG_COLOR_TYPE_GRAY;
+ } else if (colorPaletteSize <= 256 && !hasNinePatch) {
+ // This grayscale has alpha and can fit within a palette.
+ // See if it is worth fitting into a palette.
+ const size_t paletteThreshold = paletteChunkSize + alphaChunkSize +
+ paletteDataChunkSize +
+ kPaletteOverheadConstant;
+ if (grayScaleAlphaDataChunkSize > paletteThreshold) {
+ return PNG_COLOR_TYPE_PALETTE;
+ }
}
- return PNG_COLOR_TYPE_RGBA;
+ return PNG_COLOR_TYPE_GRAY_ALPHA;
+ }
+
+ if (colorPaletteSize <= 256 && !hasNinePatch) {
+ // This image can fit inside a palette. Let's see if it is worth it.
+ size_t totalSizeWithPalette = paletteDataChunkSize + paletteChunkSize;
+ size_t totalSizeWithoutPalette = colorDataChunkSize;
+ if (alphaPaletteSize > 0) {
+ totalSizeWithPalette += alphaPaletteSize;
+ totalSizeWithoutPalette = colorAlphaDataChunkSize;
+ }
+
+ if (totalSizeWithoutPalette >
+ totalSizeWithPalette + kPaletteOverheadConstant) {
+ return PNG_COLOR_TYPE_PALETTE;
+ }
+ }
+
+ if (convertibleToGrayScale) {
+ if (alphaPaletteSize == 0) {
+ return PNG_COLOR_TYPE_GRAY;
+ } else {
+ return PNG_COLOR_TYPE_GRAY_ALPHA;
+ }
+ }
+
+ if (alphaPaletteSize == 0) {
+ return PNG_COLOR_TYPE_RGB;
+ }
+ return PNG_COLOR_TYPE_RGBA;
}
-// Assigns indices to the color and alpha palettes, encodes them, and then invokes
+// Assigns indices to the color and alpha palettes, encodes them, and then
+// invokes
// png_set_PLTE/png_set_tRNS.
// This must be done before writing image data.
-// Image data must be transformed to use the indices assigned within the palette.
+// Image data must be transformed to use the indices assigned within the
+// palette.
static void writePalette(png_structp writePtr, png_infop writeInfoPtr,
- std::unordered_map<uint32_t, int>* colorPalette,
- std::unordered_set<uint32_t>* alphaPalette) {
- assert(colorPalette->size() <= 256);
- assert(alphaPalette->size() <= 256);
+ std::unordered_map<uint32_t, int>* colorPalette,
+ std::unordered_set<uint32_t>* alphaPalette) {
+ assert(colorPalette->size() <= 256);
+ assert(alphaPalette->size() <= 256);
- // Populate the PNG palette struct and assign indices to the color
- // palette.
+ // Populate the PNG palette struct and assign indices to the color
+ // palette.
- // Colors in the alpha palette should have smaller indices.
- // This will ensure that we can truncate the alpha palette if it is
- // smaller than the color palette.
- int index = 0;
- for (uint32_t color : *alphaPalette) {
- (*colorPalette)[color] = index++;
+ // Colors in the alpha palette should have smaller indices.
+ // This will ensure that we can truncate the alpha palette if it is
+ // smaller than the color palette.
+ int index = 0;
+ for (uint32_t color : *alphaPalette) {
+ (*colorPalette)[color] = index++;
+ }
+
+ // Assign the rest of the entries.
+ for (auto& entry : *colorPalette) {
+ if (entry.second == -1) {
+ entry.second = index++;
}
+ }
- // Assign the rest of the entries.
- for (auto& entry : *colorPalette) {
- if (entry.second == -1) {
- entry.second = index++;
- }
+ // Create the PNG color palette struct.
+ auto colorPaletteBytes =
+ std::unique_ptr<png_color[]>(new png_color[colorPalette->size()]);
+
+ std::unique_ptr<png_byte[]> alphaPaletteBytes;
+ if (!alphaPalette->empty()) {
+ alphaPaletteBytes =
+ std::unique_ptr<png_byte[]>(new png_byte[alphaPalette->size()]);
+ }
+
+ for (const auto& entry : *colorPalette) {
+ const uint32_t color = entry.first;
+ const int index = entry.second;
+ assert(index >= 0);
+ assert(static_cast<size_t>(index) < colorPalette->size());
+
+ png_colorp slot = colorPaletteBytes.get() + index;
+ slot->red = color >> 24;
+ slot->green = color >> 16;
+ slot->blue = color >> 8;
+
+ const png_byte alpha = color & 0x000000ff;
+ if (alpha != 0xff && alphaPaletteBytes) {
+ assert(static_cast<size_t>(index) < alphaPalette->size());
+ alphaPaletteBytes[index] = alpha;
}
+ }
- // Create the PNG color palette struct.
- auto colorPaletteBytes = std::unique_ptr<png_color[]>(new png_color[colorPalette->size()]);
+ // The bytes get copied here, so it is safe to release colorPaletteBytes at
+ // the end of function
+ // scope.
+ png_set_PLTE(writePtr, writeInfoPtr, colorPaletteBytes.get(),
+ colorPalette->size());
- std::unique_ptr<png_byte[]> alphaPaletteBytes;
- if (!alphaPalette->empty()) {
- alphaPaletteBytes = std::unique_ptr<png_byte[]>(new png_byte[alphaPalette->size()]);
- }
-
- for (const auto& entry : *colorPalette) {
- const uint32_t color = entry.first;
- const int index = entry.second;
- assert(index >= 0);
- assert(static_cast<size_t>(index) < colorPalette->size());
-
- png_colorp slot = colorPaletteBytes.get() + index;
- slot->red = color >> 24;
- slot->green = color >> 16;
- slot->blue = color >> 8;
-
- const png_byte alpha = color & 0x000000ff;
- if (alpha != 0xff && alphaPaletteBytes) {
- assert(static_cast<size_t>(index) < alphaPalette->size());
- alphaPaletteBytes[index] = alpha;
- }
- }
-
- // The bytes get copied here, so it is safe to release colorPaletteBytes at the end of function
- // scope.
- png_set_PLTE(writePtr, writeInfoPtr, colorPaletteBytes.get(), colorPalette->size());
-
- if (alphaPaletteBytes) {
- png_set_tRNS(writePtr, writeInfoPtr, alphaPaletteBytes.get(), alphaPalette->size(),
- nullptr);
- }
+ if (alphaPaletteBytes) {
+ png_set_tRNS(writePtr, writeInfoPtr, alphaPaletteBytes.get(),
+ alphaPalette->size(), nullptr);
+ }
}
// Write the 9-patch custom PNG chunks to writeInfoPtr. This must be done before
// writing image data.
static void writeNinePatch(png_structp writePtr, png_infop writeInfoPtr,
const NinePatch* ninePatch) {
- // The order of the chunks is important.
- // 9-patch code in older platforms expects the 9-patch chunk to
- // be last.
+ // The order of the chunks is important.
+ // 9-patch code in older platforms expects the 9-patch chunk to
+ // be last.
- png_unknown_chunk unknownChunks[3];
- memset(unknownChunks, 0, sizeof(unknownChunks));
+ png_unknown_chunk unknownChunks[3];
+ memset(unknownChunks, 0, sizeof(unknownChunks));
- size_t index = 0;
- size_t chunkLen = 0;
+ size_t index = 0;
+ size_t chunkLen = 0;
- std::unique_ptr<uint8_t[]> serializedOutline =
- ninePatch->serializeRoundedRectOutline(&chunkLen);
- strcpy((char*) unknownChunks[index].name, "npOl");
+ std::unique_ptr<uint8_t[]> serializedOutline =
+ ninePatch->serializeRoundedRectOutline(&chunkLen);
+ strcpy((char*)unknownChunks[index].name, "npOl");
+ unknownChunks[index].size = chunkLen;
+ unknownChunks[index].data = (png_bytep)serializedOutline.get();
+ unknownChunks[index].location = PNG_HAVE_PLTE;
+ index++;
+
+ std::unique_ptr<uint8_t[]> serializedLayoutBounds;
+ if (ninePatch->layoutBounds.nonZero()) {
+ serializedLayoutBounds = ninePatch->serializeLayoutBounds(&chunkLen);
+ strcpy((char*)unknownChunks[index].name, "npLb");
unknownChunks[index].size = chunkLen;
- unknownChunks[index].data = (png_bytep) serializedOutline.get();
+ unknownChunks[index].data = (png_bytep)serializedLayoutBounds.get();
unknownChunks[index].location = PNG_HAVE_PLTE;
index++;
+ }
- std::unique_ptr<uint8_t[]> serializedLayoutBounds;
- if (ninePatch->layoutBounds.nonZero()) {
- serializedLayoutBounds = ninePatch->serializeLayoutBounds(&chunkLen);
- strcpy((char*) unknownChunks[index].name, "npLb");
- unknownChunks[index].size = chunkLen;
- unknownChunks[index].data = (png_bytep) serializedLayoutBounds.get();
- unknownChunks[index].location = PNG_HAVE_PLTE;
- index++;
- }
+ std::unique_ptr<uint8_t[]> serializedNinePatch =
+ ninePatch->serializeBase(&chunkLen);
+ strcpy((char*)unknownChunks[index].name, "npTc");
+ unknownChunks[index].size = chunkLen;
+ unknownChunks[index].data = (png_bytep)serializedNinePatch.get();
+ unknownChunks[index].location = PNG_HAVE_PLTE;
+ index++;
- std::unique_ptr<uint8_t[]> serializedNinePatch = ninePatch->serializeBase(&chunkLen);
- strcpy((char*) unknownChunks[index].name, "npTc");
- unknownChunks[index].size = chunkLen;
- unknownChunks[index].data = (png_bytep) serializedNinePatch.get();
- unknownChunks[index].location = PNG_HAVE_PLTE;
- index++;
+ // Handle all unknown chunks. We are manually setting the chunks here,
+ // so we will only ever handle our custom chunks.
+ png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0);
- // Handle all unknown chunks. We are manually setting the chunks here,
- // so we will only ever handle our custom chunks.
- png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0);
-
- // Set the actual chunks here. The data gets copied, so our buffers can
- // safely go out of scope.
- png_set_unknown_chunks(writePtr, writeInfoPtr, unknownChunks, index);
+ // Set the actual chunks here. The data gets copied, so our buffers can
+ // safely go out of scope.
+ png_set_unknown_chunks(writePtr, writeInfoPtr, unknownChunks, index);
}
-bool writePng(IAaptContext* context, const Image* image, const NinePatch* ninePatch,
- io::OutputStream* out, const PngOptions& options) {
- // Create and initialize the write png_struct with the default error and warning handlers.
- // The header version is also passed in to ensure that this was built against the same
- // version of libpng.
- png_structp writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
- nullptr, nullptr, nullptr);
- if (writePtr == nullptr) {
- context->getDiagnostics()->error(DiagMessage()
- << "failed to create libpng write png_struct");
- return false;
+bool writePng(IAaptContext* context, const Image* image,
+ const NinePatch* ninePatch, io::OutputStream* out,
+ const PngOptions& options) {
+ // Create and initialize the write png_struct with the default error and
+ // warning handlers.
+ // The header version is also passed in to ensure that this was built against
+ // the same
+ // version of libpng.
+ png_structp writePtr =
+ png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (writePtr == nullptr) {
+ context->getDiagnostics()->error(
+ DiagMessage() << "failed to create libpng write png_struct");
+ return false;
+ }
+
+ // Allocate memory to store image header data.
+ png_infop writeInfoPtr = png_create_info_struct(writePtr);
+ if (writeInfoPtr == nullptr) {
+ context->getDiagnostics()->error(
+ DiagMessage() << "failed to create libpng write png_info");
+ png_destroy_write_struct(&writePtr, nullptr);
+ return false;
+ }
+
+ // Automatically release PNG resources at end of scope.
+ PngWriteStructDeleter pngWriteDeleter(writePtr, writeInfoPtr);
+
+ // libpng uses longjmp to jump to error handling routines.
+ // setjmp will return true only if it was jumped to, aka, there was an error.
+ if (setjmp(png_jmpbuf(writePtr))) {
+ return false;
+ }
+
+ // Handle warnings with our IDiagnostics.
+ png_set_error_fn(writePtr, (png_voidp)context->getDiagnostics(), logError,
+ logWarning);
+
+ // Set up the write functions which write to our custom data sources.
+ png_set_write_fn(writePtr, (png_voidp)out, writeDataToStream, nullptr);
+
+ // We want small files and can take the performance hit to achieve this goal.
+ png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
+
+ // Begin analysis of the image data.
+ // Scan the entire image and determine if:
+ // 1. Every pixel has R == G == B (grayscale)
+ // 2. Every pixel has A == 255 (opaque)
+ // 3. There are no more than 256 distinct RGBA colors (palette).
+ std::unordered_map<uint32_t, int> colorPalette;
+ std::unordered_set<uint32_t> alphaPalette;
+ bool needsToZeroRGBChannelsOfTransparentPixels = false;
+ bool grayScale = true;
+ int maxGrayDeviation = 0;
+
+ for (int32_t y = 0; y < image->height; y++) {
+ const uint8_t* row = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int red = *row++;
+ int green = *row++;
+ int blue = *row++;
+ int alpha = *row++;
+
+ if (alpha == 0) {
+ // The color is completely transparent.
+ // For purposes of palettes and grayscale optimization,
+ // treat all channels as 0x00.
+ needsToZeroRGBChannelsOfTransparentPixels =
+ needsToZeroRGBChannelsOfTransparentPixels ||
+ (red != 0 || green != 0 || blue != 0);
+ red = green = blue = 0;
+ }
+
+ // Insert the color into the color palette.
+ const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha;
+ colorPalette[color] = -1;
+
+ // If the pixel has non-opaque alpha, insert it into the
+ // alpha palette.
+ if (alpha != 0xff) {
+ alphaPalette.insert(color);
+ }
+
+ // Check if the image is indeed grayscale.
+ if (grayScale) {
+ if (red != green || red != blue) {
+ grayScale = false;
+ }
+ }
+
+ // Calculate the gray scale deviation so that it can be compared
+ // with the threshold.
+ maxGrayDeviation = std::max(std::abs(red - green), maxGrayDeviation);
+ maxGrayDeviation = std::max(std::abs(green - blue), maxGrayDeviation);
+ maxGrayDeviation = std::max(std::abs(blue - red), maxGrayDeviation);
}
+ }
- // Allocate memory to store image header data.
- png_infop writeInfoPtr = png_create_info_struct(writePtr);
- if (writeInfoPtr == nullptr) {
- context->getDiagnostics()->error(DiagMessage() << "failed to create libpng write png_info");
- png_destroy_write_struct(&writePtr, nullptr);
- return false;
+ if (context->verbose()) {
+ DiagMessage msg;
+ msg << " paletteSize=" << colorPalette.size()
+ << " alphaPaletteSize=" << alphaPalette.size()
+ << " maxGrayDeviation=" << maxGrayDeviation
+ << " grayScale=" << (grayScale ? "true" : "false");
+ context->getDiagnostics()->note(msg);
+ }
+
+ const bool convertibleToGrayScale =
+ maxGrayDeviation <= options.grayScaleTolerance;
+
+ const int newColorType = pickColorType(
+ image->width, image->height, grayScale, convertibleToGrayScale,
+ ninePatch != nullptr, colorPalette.size(), alphaPalette.size());
+
+ if (context->verbose()) {
+ DiagMessage msg;
+ msg << "encoding PNG ";
+ if (ninePatch) {
+ msg << "(with 9-patch) as ";
}
-
- // Automatically release PNG resources at end of scope.
- PngWriteStructDeleter pngWriteDeleter(writePtr, writeInfoPtr);
-
- // libpng uses longjmp to jump to error handling routines.
- // setjmp will return true only if it was jumped to, aka, there was an error.
- if (setjmp(png_jmpbuf(writePtr))) {
- return false;
+ switch (newColorType) {
+ case PNG_COLOR_TYPE_GRAY:
+ msg << "GRAY";
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ msg << "GRAY + ALPHA";
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ msg << "RGB";
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ msg << "RGBA";
+ break;
+ case PNG_COLOR_TYPE_PALETTE:
+ msg << "PALETTE";
+ break;
+ default:
+ msg << "unknown type " << newColorType;
+ break;
}
+ context->getDiagnostics()->note(msg);
+ }
- // Handle warnings with our IDiagnostics.
- png_set_error_fn(writePtr, (png_voidp) context->getDiagnostics(), logError, logWarning);
+ png_set_IHDR(writePtr, writeInfoPtr, image->width, image->height, 8,
+ newColorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
- // Set up the write functions which write to our custom data sources.
- png_set_write_fn(writePtr, (png_voidp) out, writeDataToStream, nullptr);
+ if (newColorType & PNG_COLOR_MASK_PALETTE) {
+ // Assigns indices to the palette, and writes the encoded palette to the
+ // libpng writePtr.
+ writePalette(writePtr, writeInfoPtr, &colorPalette, &alphaPalette);
+ png_set_filter(writePtr, 0, PNG_NO_FILTERS);
+ } else {
+ png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
+ }
- // We want small files and can take the performance hit to achieve this goal.
- png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
+ if (ninePatch) {
+ writeNinePatch(writePtr, writeInfoPtr, ninePatch);
+ }
- // Begin analysis of the image data.
- // Scan the entire image and determine if:
- // 1. Every pixel has R == G == B (grayscale)
- // 2. Every pixel has A == 255 (opaque)
- // 3. There are no more than 256 distinct RGBA colors (palette).
- std::unordered_map<uint32_t, int> colorPalette;
- std::unordered_set<uint32_t> alphaPalette;
- bool needsToZeroRGBChannelsOfTransparentPixels = false;
- bool grayScale = true;
- int maxGrayDeviation = 0;
+ // Flush our updates to the header.
+ png_write_info(writePtr, writeInfoPtr);
+
+ // Write out each row of image data according to its encoding.
+ if (newColorType == PNG_COLOR_TYPE_PALETTE) {
+ // 1 byte/pixel.
+ auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width]);
for (int32_t y = 0; y < image->height; y++) {
- const uint8_t* row = image->rows[y];
- for (int32_t x = 0; x < image->width; x++) {
- int red = *row++;
- int green = *row++;
- int blue = *row++;
- int alpha = *row++;
-
- if (alpha == 0) {
- // The color is completely transparent.
- // For purposes of palettes and grayscale optimization,
- // treat all channels as 0x00.
- needsToZeroRGBChannelsOfTransparentPixels =
- needsToZeroRGBChannelsOfTransparentPixels ||
- (red != 0 || green != 0 || blue != 0);
- red = green = blue = 0;
- }
-
- // Insert the color into the color palette.
- const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha;
- colorPalette[color] = -1;
-
- // If the pixel has non-opaque alpha, insert it into the
- // alpha palette.
- if (alpha != 0xff) {
- alphaPalette.insert(color);
- }
-
- // Check if the image is indeed grayscale.
- if (grayScale) {
- if (red != green || red != blue) {
- grayScale = false;
- }
- }
-
- // Calculate the gray scale deviation so that it can be compared
- // with the threshold.
- maxGrayDeviation = std::max(std::abs(red - green), maxGrayDeviation);
- maxGrayDeviation = std::max(std::abs(green - blue), maxGrayDeviation);
- maxGrayDeviation = std::max(std::abs(blue - red), maxGrayDeviation);
+ png_const_bytep inRow = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int rr = *inRow++;
+ int gg = *inRow++;
+ int bb = *inRow++;
+ int aa = *inRow++;
+ if (aa == 0) {
+ // Zero out color channels when transparent.
+ rr = gg = bb = 0;
}
+
+ const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa;
+ const int idx = colorPalette[color];
+ assert(idx != -1);
+ outRow[x] = static_cast<png_byte>(idx);
+ }
+ png_write_row(writePtr, outRow.get());
}
+ } else if (newColorType == PNG_COLOR_TYPE_GRAY ||
+ newColorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ const size_t bpp = newColorType == PNG_COLOR_TYPE_GRAY ? 1 : 2;
+ auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
- if (context->verbose()) {
- DiagMessage msg;
- msg << " paletteSize=" << colorPalette.size()
- << " alphaPaletteSize=" << alphaPalette.size()
- << " maxGrayDeviation=" << maxGrayDeviation
- << " grayScale=" << (grayScale ? "true" : "false");
- context->getDiagnostics()->note(msg);
- }
-
- const bool convertibleToGrayScale = maxGrayDeviation <= options.grayScaleTolerance;
-
- const int newColorType = pickColorType(image->width, image->height, grayScale,
- convertibleToGrayScale, ninePatch != nullptr,
- colorPalette.size(), alphaPalette.size());
-
- if (context->verbose()) {
- DiagMessage msg;
- msg << "encoding PNG ";
- if (ninePatch) {
- msg << "(with 9-patch) as ";
+ for (int32_t y = 0; y < image->height; y++) {
+ png_const_bytep inRow = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int rr = inRow[x * 4];
+ int gg = inRow[x * 4 + 1];
+ int bb = inRow[x * 4 + 2];
+ int aa = inRow[x * 4 + 3];
+ if (aa == 0) {
+ // Zero out the gray channel when transparent.
+ rr = gg = bb = 0;
}
- switch (newColorType) {
- case PNG_COLOR_TYPE_GRAY:
- msg << "GRAY";
- break;
- case PNG_COLOR_TYPE_GRAY_ALPHA:
- msg << "GRAY + ALPHA";
- break;
- case PNG_COLOR_TYPE_RGB:
- msg << "RGB";
- break;
- case PNG_COLOR_TYPE_RGB_ALPHA:
- msg << "RGBA";
- break;
- case PNG_COLOR_TYPE_PALETTE:
- msg << "PALETTE";
- break;
- default:
- msg << "unknown type " << newColorType;
- break;
- }
- context->getDiagnostics()->note(msg);
- }
- png_set_IHDR(writePtr, writeInfoPtr, image->width, image->height, 8, newColorType,
- PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
-
- if (newColorType & PNG_COLOR_MASK_PALETTE) {
- // Assigns indices to the palette, and writes the encoded palette to the libpng writePtr.
- writePalette(writePtr, writeInfoPtr, &colorPalette, &alphaPalette);
- png_set_filter(writePtr, 0, PNG_NO_FILTERS);
- } else {
- png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
- }
-
- if (ninePatch) {
- writeNinePatch(writePtr, writeInfoPtr, ninePatch);
- }
-
- // Flush our updates to the header.
- png_write_info(writePtr, writeInfoPtr);
-
- // Write out each row of image data according to its encoding.
- if (newColorType == PNG_COLOR_TYPE_PALETTE) {
- // 1 byte/pixel.
- auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width]);
-
- for (int32_t y = 0; y < image->height; y++) {
- png_const_bytep inRow = image->rows[y];
- for (int32_t x = 0; x < image->width; x++) {
- int rr = *inRow++;
- int gg = *inRow++;
- int bb = *inRow++;
- int aa = *inRow++;
- if (aa == 0) {
- // Zero out color channels when transparent.
- rr = gg = bb = 0;
- }
-
- const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa;
- const int idx = colorPalette[color];
- assert(idx != -1);
- outRow[x] = static_cast<png_byte>(idx);
- }
- png_write_row(writePtr, outRow.get());
- }
- } else if (newColorType == PNG_COLOR_TYPE_GRAY || newColorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
- const size_t bpp = newColorType == PNG_COLOR_TYPE_GRAY ? 1 : 2;
- auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
-
- for (int32_t y = 0; y < image->height; y++) {
- png_const_bytep inRow = image->rows[y];
- for (int32_t x = 0; x < image->width; x++) {
- int rr = inRow[x * 4];
- int gg = inRow[x * 4 + 1];
- int bb = inRow[x * 4 + 2];
- int aa = inRow[x * 4 + 3];
- if (aa == 0) {
- // Zero out the gray channel when transparent.
- rr = gg = bb = 0;
- }
-
- if (grayScale) {
- // The image was already grayscale, red == green == blue.
- outRow[x * bpp] = inRow[x * 4];
- } else {
- // The image is convertible to grayscale, use linear-luminance of
- // sRGB colorspace: https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
- outRow[x * bpp] = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
- }
-
- if (bpp == 2) {
- // Write out alpha if we have it.
- outRow[x * bpp + 1] = aa;
- }
- }
- png_write_row(writePtr, outRow.get());
- }
- } else if (newColorType == PNG_COLOR_TYPE_RGB || newColorType == PNG_COLOR_TYPE_RGBA) {
- const size_t bpp = newColorType == PNG_COLOR_TYPE_RGB ? 3 : 4;
- if (needsToZeroRGBChannelsOfTransparentPixels) {
- // The source RGBA data can't be used as-is, because we need to zero out the RGB
- // values of transparent pixels.
- auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
-
- for (int32_t y = 0; y < image->height; y++) {
- png_const_bytep inRow = image->rows[y];
- for (int32_t x = 0; x < image->width; x++) {
- int rr = *inRow++;
- int gg = *inRow++;
- int bb = *inRow++;
- int aa = *inRow++;
- if (aa == 0) {
- // Zero out the RGB channels when transparent.
- rr = gg = bb = 0;
- }
- outRow[x * bpp] = rr;
- outRow[x * bpp + 1] = gg;
- outRow[x * bpp + 2] = bb;
- if (bpp == 4) {
- outRow[x * bpp + 3] = aa;
- }
- }
- png_write_row(writePtr, outRow.get());
- }
+ if (grayScale) {
+ // The image was already grayscale, red == green == blue.
+ outRow[x * bpp] = inRow[x * 4];
} else {
- // The source image can be used as-is, just tell libpng whether or not to ignore
- // the alpha channel.
- if (newColorType == PNG_COLOR_TYPE_RGB) {
- // Delete the extraneous alpha values that we appended to our buffer
- // when reading the original values.
- png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
- }
- png_write_image(writePtr, image->rows.get());
+ // The image is convertible to grayscale, use linear-luminance of
+ // sRGB colorspace:
+ // https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
+ outRow[x * bpp] =
+ (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
}
- } else {
- assert(false && "unreachable");
- }
- png_write_end(writePtr, writeInfoPtr);
- return true;
+ if (bpp == 2) {
+ // Write out alpha if we have it.
+ outRow[x * bpp + 1] = aa;
+ }
+ }
+ png_write_row(writePtr, outRow.get());
+ }
+ } else if (newColorType == PNG_COLOR_TYPE_RGB ||
+ newColorType == PNG_COLOR_TYPE_RGBA) {
+ const size_t bpp = newColorType == PNG_COLOR_TYPE_RGB ? 3 : 4;
+ if (needsToZeroRGBChannelsOfTransparentPixels) {
+ // The source RGBA data can't be used as-is, because we need to zero out
+ // the RGB
+ // values of transparent pixels.
+ auto outRow =
+ std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
+
+ for (int32_t y = 0; y < image->height; y++) {
+ png_const_bytep inRow = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int rr = *inRow++;
+ int gg = *inRow++;
+ int bb = *inRow++;
+ int aa = *inRow++;
+ if (aa == 0) {
+ // Zero out the RGB channels when transparent.
+ rr = gg = bb = 0;
+ }
+ outRow[x * bpp] = rr;
+ outRow[x * bpp + 1] = gg;
+ outRow[x * bpp + 2] = bb;
+ if (bpp == 4) {
+ outRow[x * bpp + 3] = aa;
+ }
+ }
+ png_write_row(writePtr, outRow.get());
+ }
+ } else {
+ // The source image can be used as-is, just tell libpng whether or not to
+ // ignore
+ // the alpha channel.
+ if (newColorType == PNG_COLOR_TYPE_RGB) {
+ // Delete the extraneous alpha values that we appended to our buffer
+ // when reading the original values.
+ png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
+ }
+ png_write_image(writePtr, image->rows.get());
+ }
+ } else {
+ assert(false && "unreachable");
+ }
+
+ png_write_end(writePtr, writeInfoPtr);
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index 732101f..d8ed0bb 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -14,235 +14,242 @@
* limitations under the License.
*/
+#include "compile/PseudolocaleGenerator.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
-#include "compile/PseudolocaleGenerator.h"
#include "compile/Pseudolocalizer.h"
#include <algorithm>
namespace aapt {
-std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string,
- Pseudolocalizer::Method method,
- StringPool* pool) {
- Pseudolocalizer localizer(method);
+std::unique_ptr<StyledString> pseudolocalizeStyledString(
+ StyledString* string, Pseudolocalizer::Method method, StringPool* pool) {
+ Pseudolocalizer localizer(method);
- const StringPiece originalText = *string->value->str;
+ const StringPiece originalText = *string->value->str;
- StyleString localized;
+ StyleString localized;
- // Copy the spans. We will update their offsets when we localize.
- localized.spans.reserve(string->value->spans.size());
- for (const StringPool::Span& span : string->value->spans) {
- localized.spans.push_back(Span{ *span.name, span.firstChar, span.lastChar });
+ // Copy the spans. We will update their offsets when we localize.
+ localized.spans.reserve(string->value->spans.size());
+ for (const StringPool::Span& span : string->value->spans) {
+ localized.spans.push_back(Span{*span.name, span.firstChar, span.lastChar});
+ }
+
+ // The ranges are all represented with a single value. This is the start of
+ // one range and
+ // end of another.
+ struct Range {
+ size_t start;
+
+ // Once the new string is localized, these are the pointers to the spans to
+ // adjust.
+ // Since this struct represents the start of one range and end of another,
+ // we have
+ // the two pointers respectively.
+ uint32_t* updateStart;
+ uint32_t* updateEnd;
+ };
+
+ auto cmp = [](const Range& r, size_t index) -> bool {
+ return r.start < index;
+ };
+
+ // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7]
+ // The ranges are the spaces in between. In this example, with a total string
+ // length of 9,
+ // the vector represents: (0,1], (2,4], (5,6], (7,9]
+ //
+ std::vector<Range> ranges;
+ ranges.push_back(Range{0});
+ ranges.push_back(Range{originalText.size() - 1});
+ for (size_t i = 0; i < string->value->spans.size(); i++) {
+ const StringPool::Span& span = string->value->spans[i];
+
+ // Insert or update the Range marker for the start of this span.
+ auto iter =
+ std::lower_bound(ranges.begin(), ranges.end(), span.firstChar, cmp);
+ if (iter != ranges.end() && iter->start == span.firstChar) {
+ iter->updateStart = &localized.spans[i].firstChar;
+ } else {
+ ranges.insert(
+ iter, Range{span.firstChar, &localized.spans[i].firstChar, nullptr});
}
- // The ranges are all represented with a single value. This is the start of one range and
- // end of another.
- struct Range {
- size_t start;
+ // Insert or update the Range marker for the end of this span.
+ iter = std::lower_bound(ranges.begin(), ranges.end(), span.lastChar, cmp);
+ if (iter != ranges.end() && iter->start == span.lastChar) {
+ iter->updateEnd = &localized.spans[i].lastChar;
+ } else {
+ ranges.insert(
+ iter, Range{span.lastChar, nullptr, &localized.spans[i].lastChar});
+ }
+ }
- // Once the new string is localized, these are the pointers to the spans to adjust.
- // Since this struct represents the start of one range and end of another, we have
- // the two pointers respectively.
- uint32_t* updateStart;
- uint32_t* updateEnd;
- };
+ localized.str += localizer.start();
- auto cmp = [](const Range& r, size_t index) -> bool {
- return r.start < index;
- };
-
- // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7]
- // The ranges are the spaces in between. In this example, with a total string length of 9,
- // the vector represents: (0,1], (2,4], (5,6], (7,9]
- //
- std::vector<Range> ranges;
- ranges.push_back(Range{ 0 });
- ranges.push_back(Range{ originalText.size() - 1 });
- for (size_t i = 0; i < string->value->spans.size(); i++) {
- const StringPool::Span& span = string->value->spans[i];
-
- // Insert or update the Range marker for the start of this span.
- auto iter = std::lower_bound(ranges.begin(), ranges.end(), span.firstChar, cmp);
- if (iter != ranges.end() && iter->start == span.firstChar) {
- iter->updateStart = &localized.spans[i].firstChar;
- } else {
- ranges.insert(iter,
- Range{ span.firstChar, &localized.spans[i].firstChar, nullptr });
- }
-
- // Insert or update the Range marker for the end of this span.
- iter = std::lower_bound(ranges.begin(), ranges.end(), span.lastChar, cmp);
- if (iter != ranges.end() && iter->start == span.lastChar) {
- iter->updateEnd = &localized.spans[i].lastChar;
- } else {
- ranges.insert(iter,
- Range{ span.lastChar, nullptr, &localized.spans[i].lastChar });
- }
+ // Iterate over the ranges and localize each section.
+ for (size_t i = 0; i < ranges.size(); i++) {
+ const size_t start = ranges[i].start;
+ size_t len = originalText.size() - start;
+ if (i + 1 < ranges.size()) {
+ len = ranges[i + 1].start - start;
}
- localized.str += localizer.start();
-
- // Iterate over the ranges and localize each section.
- for (size_t i = 0; i < ranges.size(); i++) {
- const size_t start = ranges[i].start;
- size_t len = originalText.size() - start;
- if (i + 1 < ranges.size()) {
- len = ranges[i + 1].start - start;
- }
-
- if (ranges[i].updateStart) {
- *ranges[i].updateStart = localized.str.size();
- }
-
- if (ranges[i].updateEnd) {
- *ranges[i].updateEnd = localized.str.size();
- }
-
- localized.str += localizer.text(originalText.substr(start, len));
+ if (ranges[i].updateStart) {
+ *ranges[i].updateStart = localized.str.size();
}
- localized.str += localizer.end();
+ if (ranges[i].updateEnd) {
+ *ranges[i].updateEnd = localized.str.size();
+ }
- std::unique_ptr<StyledString> localizedString = util::make_unique<StyledString>(
- pool->makeRef(localized));
- localizedString->setSource(string->getSource());
- return localizedString;
+ localized.str += localizer.text(originalText.substr(start, len));
+ }
+
+ localized.str += localizer.end();
+
+ std::unique_ptr<StyledString> localizedString =
+ util::make_unique<StyledString>(pool->makeRef(localized));
+ localizedString->setSource(string->getSource());
+ return localizedString;
}
namespace {
struct Visitor : public RawValueVisitor {
- StringPool* mPool;
- Pseudolocalizer::Method mMethod;
- Pseudolocalizer mLocalizer;
+ StringPool* mPool;
+ Pseudolocalizer::Method mMethod;
+ Pseudolocalizer mLocalizer;
- // Either value or item will be populated upon visiting the value.
- std::unique_ptr<Value> mValue;
- std::unique_ptr<Item> mItem;
+ // Either value or item will be populated upon visiting the value.
+ std::unique_ptr<Value> mValue;
+ std::unique_ptr<Item> mItem;
- Visitor(StringPool* pool, Pseudolocalizer::Method method) :
- mPool(pool), mMethod(method), mLocalizer(method) {
- }
+ Visitor(StringPool* pool, Pseudolocalizer::Method method)
+ : mPool(pool), mMethod(method), mLocalizer(method) {}
- void visit(Plural* plural) override {
- std::unique_ptr<Plural> localized = util::make_unique<Plural>();
- for (size_t i = 0; i < plural->values.size(); i++) {
- Visitor subVisitor(mPool, mMethod);
- if (plural->values[i]) {
- plural->values[i]->accept(&subVisitor);
- if (subVisitor.mValue) {
- localized->values[i] = std::move(subVisitor.mItem);
- } else {
- localized->values[i] = std::unique_ptr<Item>(plural->values[i]->clone(mPool));
- }
- }
+ void visit(Plural* plural) override {
+ std::unique_ptr<Plural> localized = util::make_unique<Plural>();
+ for (size_t i = 0; i < plural->values.size(); i++) {
+ Visitor subVisitor(mPool, mMethod);
+ if (plural->values[i]) {
+ plural->values[i]->accept(&subVisitor);
+ if (subVisitor.mValue) {
+ localized->values[i] = std::move(subVisitor.mItem);
+ } else {
+ localized->values[i] =
+ std::unique_ptr<Item>(plural->values[i]->clone(mPool));
}
- localized->setSource(plural->getSource());
- localized->setWeak(true);
- mValue = std::move(localized);
+ }
}
+ localized->setSource(plural->getSource());
+ localized->setWeak(true);
+ mValue = std::move(localized);
+ }
- void visit(String* string) override {
- std::string result = mLocalizer.start() + mLocalizer.text(*string->value) +
- mLocalizer.end();
- std::unique_ptr<String> localized = util::make_unique<String>(mPool->makeRef(result));
- localized->setSource(string->getSource());
- localized->setWeak(true);
- mItem = std::move(localized);
- }
+ void visit(String* string) override {
+ std::string result =
+ mLocalizer.start() + mLocalizer.text(*string->value) + mLocalizer.end();
+ std::unique_ptr<String> localized =
+ util::make_unique<String>(mPool->makeRef(result));
+ localized->setSource(string->getSource());
+ localized->setWeak(true);
+ mItem = std::move(localized);
+ }
- void visit(StyledString* string) override {
- mItem = pseudolocalizeStyledString(string, mMethod, mPool);
- mItem->setWeak(true);
- }
+ void visit(StyledString* string) override {
+ mItem = pseudolocalizeStyledString(string, mMethod, mPool);
+ mItem->setWeak(true);
+ }
};
ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base,
Pseudolocalizer::Method m) {
- ConfigDescription modified = base;
- switch (m) {
+ ConfigDescription modified = base;
+ switch (m) {
case Pseudolocalizer::Method::kAccent:
- modified.language[0] = 'e';
- modified.language[1] = 'n';
- modified.country[0] = 'X';
- modified.country[1] = 'A';
- break;
+ modified.language[0] = 'e';
+ modified.language[1] = 'n';
+ modified.country[0] = 'X';
+ modified.country[1] = 'A';
+ break;
case Pseudolocalizer::Method::kBidi:
- modified.language[0] = 'a';
- modified.language[1] = 'r';
- modified.country[0] = 'X';
- modified.country[1] = 'B';
- break;
+ modified.language[0] = 'a';
+ modified.language[1] = 'r';
+ modified.country[0] = 'X';
+ modified.country[1] = 'B';
+ break;
default:
- break;
- }
- return modified;
+ break;
+ }
+ return modified;
}
void pseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
ResourceConfigValue* originalValue,
- StringPool* pool,
- ResourceEntry* entry) {
- Visitor visitor(pool, method);
- originalValue->value->accept(&visitor);
+ StringPool* pool, ResourceEntry* entry) {
+ Visitor visitor(pool, method);
+ originalValue->value->accept(&visitor);
- std::unique_ptr<Value> localizedValue;
- if (visitor.mValue) {
- localizedValue = std::move(visitor.mValue);
- } else if (visitor.mItem) {
- localizedValue = std::move(visitor.mItem);
- }
+ std::unique_ptr<Value> localizedValue;
+ if (visitor.mValue) {
+ localizedValue = std::move(visitor.mValue);
+ } else if (visitor.mItem) {
+ localizedValue = std::move(visitor.mItem);
+ }
- if (!localizedValue) {
- return;
- }
+ if (!localizedValue) {
+ return;
+ }
- ConfigDescription configWithAccent = modifyConfigForPseudoLocale(
- originalValue->config, method);
+ ConfigDescription configWithAccent =
+ modifyConfigForPseudoLocale(originalValue->config, method);
- ResourceConfigValue* newConfigValue = entry->findOrCreateValue(
- configWithAccent, originalValue->product);
- if (!newConfigValue->value) {
- // Only use auto-generated pseudo-localization if none is defined.
- newConfigValue->value = std::move(localizedValue);
- }
+ ResourceConfigValue* newConfigValue =
+ entry->findOrCreateValue(configWithAccent, originalValue->product);
+ if (!newConfigValue->value) {
+ // Only use auto-generated pseudo-localization if none is defined.
+ newConfigValue->value = std::move(localizedValue);
+ }
}
/**
- * A value is pseudolocalizable if it does not define a locale (or is the default locale)
+ * A value is pseudolocalizable if it does not define a locale (or is the
+ * default locale)
* and is translateable.
*/
static bool isPseudolocalizable(ResourceConfigValue* configValue) {
- const int diff = configValue->config.diff(ConfigDescription::defaultConfig());
- if (diff & ConfigDescription::CONFIG_LOCALE) {
- return false;
- }
- return configValue->value->isTranslateable();
+ const int diff = configValue->config.diff(ConfigDescription::defaultConfig());
+ if (diff & ConfigDescription::CONFIG_LOCALE) {
+ return false;
+ }
+ return configValue->value->isTranslateable();
}
-} // namespace
+} // namespace
-bool PseudolocaleGenerator::consume(IAaptContext* context, ResourceTable* table) {
- for (auto& package : table->packages) {
- for (auto& type : package->types) {
- for (auto& entry : type->entries) {
- std::vector<ResourceConfigValue*> values = entry->findValuesIf(isPseudolocalizable);
+bool PseudolocaleGenerator::consume(IAaptContext* context,
+ ResourceTable* table) {
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ std::vector<ResourceConfigValue*> values =
+ entry->findValuesIf(isPseudolocalizable);
- for (ResourceConfigValue* value : values) {
- pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
- &table->stringPool, entry.get());
- pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
- &table->stringPool, entry.get());
- }
- }
+ for (ResourceConfigValue* value : values) {
+ pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
+ &table->stringPool, entry.get());
+ pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
+ &table->stringPool, entry.get());
}
+ }
}
- return true;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.h b/tools/aapt2/compile/PseudolocaleGenerator.h
index 4fbc516..4e97cb9 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.h
+++ b/tools/aapt2/compile/PseudolocaleGenerator.h
@@ -23,14 +23,13 @@
namespace aapt {
-std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string,
- Pseudolocalizer::Method method,
- StringPool* pool);
+std::unique_ptr<StyledString> pseudolocalizeStyledString(
+ StyledString* string, Pseudolocalizer::Method method, StringPool* pool);
struct PseudolocaleGenerator : public IResourceTableConsumer {
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ bool consume(IAaptContext* context, ResourceTable* table) override;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H */
diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
index 1f62f90..64a3e97 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
@@ -23,99 +23,110 @@
namespace aapt {
TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) {
- StringPool pool;
- StyleString originalStyle;
- originalStyle.str = "Hello world!";
- originalStyle.spans = { Span{ "b", 2, 3 }, Span{ "b", 6, 7 }, Span{ "i", 1, 10 } };
+ StringPool pool;
+ StyleString originalStyle;
+ originalStyle.str = "Hello world!";
+ originalStyle.spans = {Span{"b", 2, 3}, Span{"b", 6, 7}, Span{"i", 1, 10}};
- std::unique_ptr<StyledString> newString = pseudolocalizeStyledString(
- util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
- Pseudolocalizer::Method::kNone, &pool);
+ std::unique_ptr<StyledString> newString = pseudolocalizeStyledString(
+ util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
+ Pseudolocalizer::Method::kNone, &pool);
- EXPECT_EQ(originalStyle.str, *newString->value->str);
- ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
+ EXPECT_EQ(originalStyle.str, *newString->value->str);
+ ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
- EXPECT_EQ(std::string("He").size(), newString->value->spans[0].firstChar);
- EXPECT_EQ(std::string("Hel").size(), newString->value->spans[0].lastChar);
- EXPECT_EQ(std::string("b"), *newString->value->spans[0].name);
+ EXPECT_EQ(std::string("He").size(), newString->value->spans[0].firstChar);
+ EXPECT_EQ(std::string("Hel").size(), newString->value->spans[0].lastChar);
+ EXPECT_EQ(std::string("b"), *newString->value->spans[0].name);
- EXPECT_EQ(std::string("Hello ").size(), newString->value->spans[1].firstChar);
- EXPECT_EQ(std::string("Hello w").size(), newString->value->spans[1].lastChar);
- EXPECT_EQ(std::string("b"), *newString->value->spans[1].name);
+ EXPECT_EQ(std::string("Hello ").size(), newString->value->spans[1].firstChar);
+ EXPECT_EQ(std::string("Hello w").size(), newString->value->spans[1].lastChar);
+ EXPECT_EQ(std::string("b"), *newString->value->spans[1].name);
- EXPECT_EQ(std::string("H").size(), newString->value->spans[2].firstChar);
- EXPECT_EQ(std::string("Hello worl").size(), newString->value->spans[2].lastChar);
- EXPECT_EQ(std::string("i"), *newString->value->spans[2].name);
+ EXPECT_EQ(std::string("H").size(), newString->value->spans[2].firstChar);
+ EXPECT_EQ(std::string("Hello worl").size(),
+ newString->value->spans[2].lastChar);
+ EXPECT_EQ(std::string("i"), *newString->value->spans[2].name);
- originalStyle.spans.push_back(Span{ "em", 0, 11u });
+ originalStyle.spans.push_back(Span{"em", 0, 11u});
- newString = pseudolocalizeStyledString(
- util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
- Pseudolocalizer::Method::kAccent, &pool);
+ newString = pseudolocalizeStyledString(
+ util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
+ Pseudolocalizer::Method::kAccent, &pool);
- EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *newString->value->str);
- ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
+ EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *newString->value->str);
+ ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
- EXPECT_EQ(std::string("[Ĥé").size(), newString->value->spans[0].firstChar);
- EXPECT_EQ(std::string("[Ĥéļ").size(), newString->value->spans[0].lastChar);
+ EXPECT_EQ(std::string("[Ĥé").size(), newString->value->spans[0].firstChar);
+ EXPECT_EQ(std::string("[Ĥéļ").size(), newString->value->spans[0].lastChar);
- EXPECT_EQ(std::string("[Ĥéļļö ").size(), newString->value->spans[1].firstChar);
- EXPECT_EQ(std::string("[Ĥéļļö ŵ").size(), newString->value->spans[1].lastChar);
+ EXPECT_EQ(std::string("[Ĥéļļö ").size(),
+ newString->value->spans[1].firstChar);
+ EXPECT_EQ(std::string("[Ĥéļļö ŵ").size(),
+ newString->value->spans[1].lastChar);
- EXPECT_EQ(std::string("[Ĥ").size(), newString->value->spans[2].firstChar);
- EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļ").size(), newString->value->spans[2].lastChar);
+ EXPECT_EQ(std::string("[Ĥ").size(), newString->value->spans[2].firstChar);
+ EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļ").size(),
+ newString->value->spans[2].lastChar);
- EXPECT_EQ(std::string("[").size(), newString->value->spans[3].firstChar);
- EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð").size(), newString->value->spans[3].lastChar);
+ EXPECT_EQ(std::string("[").size(), newString->value->spans[3].firstChar);
+ EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð").size(),
+ newString->value->spans[3].lastChar);
}
TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addString("android:string/one", "one")
- .addString("android:string/two", ResourceId{}, test::parseConfigOrDie("en"), "two")
- .addString("android:string/three", "three")
- .addString("android:string/three", ResourceId{}, test::parseConfigOrDie("en-rXA"),
- "three")
- .addString("android:string/four", "four")
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .addString("android:string/one", "one")
+ .addString("android:string/two", ResourceId{},
+ test::parseConfigOrDie("en"), "two")
+ .addString("android:string/three", "three")
+ .addString("android:string/three", ResourceId{},
+ test::parseConfigOrDie("en-rXA"), "three")
+ .addString("android:string/four", "four")
+ .build();
- String* val = test::getValue<String>(table.get(), "android:string/four");
- ASSERT_NE(nullptr, val);
- val->setTranslateable(false);
+ String* val = test::getValue<String>(table.get(), "android:string/four");
+ ASSERT_NE(nullptr, val);
+ val->setTranslateable(false);
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- PseudolocaleGenerator generator;
- ASSERT_TRUE(generator.consume(context.get(), table.get()));
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ PseudolocaleGenerator generator;
+ ASSERT_TRUE(generator.consume(context.get(), table.get()));
- // Normal pseudolocalization should take place.
- ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "android:string/one",
- test::parseConfigOrDie("en-rXA")));
- ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "android:string/one",
- test::parseConfigOrDie("ar-rXB")));
+ // Normal pseudolocalization should take place.
+ ASSERT_NE(nullptr,
+ test::getValueForConfig<String>(table.get(), "android:string/one",
+ test::parseConfigOrDie("en-rXA")));
+ ASSERT_NE(nullptr,
+ test::getValueForConfig<String>(table.get(), "android:string/one",
+ test::parseConfigOrDie("ar-rXB")));
- // No default config for android:string/two, so no pseudlocales should exist.
- ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/two",
- test::parseConfigOrDie("en-rXA")));
- ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/two",
- test::parseConfigOrDie("ar-rXB")));
+ // No default config for android:string/two, so no pseudlocales should exist.
+ ASSERT_EQ(nullptr,
+ test::getValueForConfig<String>(table.get(), "android:string/two",
+ test::parseConfigOrDie("en-rXA")));
+ ASSERT_EQ(nullptr,
+ test::getValueForConfig<String>(table.get(), "android:string/two",
+ test::parseConfigOrDie("ar-rXB")));
+ // Check that we didn't override manual pseudolocalization.
+ val = test::getValueForConfig<String>(table.get(), "android:string/three",
+ test::parseConfigOrDie("en-rXA"));
+ ASSERT_NE(nullptr, val);
+ EXPECT_EQ(std::string("three"), *val->value);
- // Check that we didn't override manual pseudolocalization.
- val = test::getValueForConfig<String>(table.get(), "android:string/three",
- test::parseConfigOrDie("en-rXA"));
- ASSERT_NE(nullptr, val);
- EXPECT_EQ(std::string("three"), *val->value);
+ ASSERT_NE(nullptr,
+ test::getValueForConfig<String>(table.get(), "android:string/three",
+ test::parseConfigOrDie("ar-rXB")));
- ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "android:string/three",
- test::parseConfigOrDie("ar-rXB")));
-
- // Check that four's translateable marker was honored.
- ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/four",
- test::parseConfigOrDie("en-rXA")));
- ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/four",
- test::parseConfigOrDie("ar-rXB")));
-
+ // Check that four's translateable marker was honored.
+ ASSERT_EQ(nullptr,
+ test::getValueForConfig<String>(table.get(), "android:string/four",
+ test::parseConfigOrDie("en-rXA")));
+ ASSERT_EQ(nullptr,
+ test::getValueForConfig<String>(table.get(), "android:string/four",
+ test::parseConfigOrDie("ar-rXB")));
}
-} // namespace aapt
-
+} // namespace aapt
diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp
index 90d0d85..c3aec98 100644
--- a/tools/aapt2/compile/Pseudolocalizer.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer.cpp
@@ -20,9 +20,10 @@
namespace aapt {
// String basis to generate expansion
-static const std::string k_expansion_string = "one two three "
- "four five six seven eight nine ten eleven twelve thirteen "
- "fourteen fiveteen sixteen seventeen nineteen twenty";
+static const std::string k_expansion_string =
+ "one two three "
+ "four five six seven eight nine ten eleven twelve thirteen "
+ "fourteen fiveteen sixteen seventeen nineteen twenty";
// Special unicode characters to override directionality of the words
static const std::string k_rlm = "\u200f";
@@ -37,229 +38,310 @@
static const char k_arg_end = '}';
class PseudoMethodNone : public PseudoMethodImpl {
-public:
- std::string text(const StringPiece& text) override { return text.toString(); }
- std::string placeholder(const StringPiece& text) override { return text.toString(); }
+ public:
+ std::string text(const StringPiece& text) override { return text.toString(); }
+ std::string placeholder(const StringPiece& text) override {
+ return text.toString();
+ }
};
class PseudoMethodBidi : public PseudoMethodImpl {
-public:
- std::string text(const StringPiece& text) override;
- std::string placeholder(const StringPiece& text) override;
+ public:
+ std::string text(const StringPiece& text) override;
+ std::string placeholder(const StringPiece& text) override;
};
class PseudoMethodAccent : public PseudoMethodImpl {
-public:
- PseudoMethodAccent() : mDepth(0), mWordCount(0), mLength(0) {}
- std::string start() override;
- std::string end() override;
- std::string text(const StringPiece& text) override;
- std::string placeholder(const StringPiece& text) override;
-private:
- size_t mDepth;
- size_t mWordCount;
- size_t mLength;
+ public:
+ PseudoMethodAccent() : mDepth(0), mWordCount(0), mLength(0) {}
+ std::string start() override;
+ std::string end() override;
+ std::string text(const StringPiece& text) override;
+ std::string placeholder(const StringPiece& text) override;
+
+ private:
+ size_t mDepth;
+ size_t mWordCount;
+ size_t mLength;
};
Pseudolocalizer::Pseudolocalizer(Method method) : mLastDepth(0) {
- setMethod(method);
+ setMethod(method);
}
void Pseudolocalizer::setMethod(Method method) {
- switch (method) {
+ switch (method) {
case Method::kNone:
- mImpl = util::make_unique<PseudoMethodNone>();
- break;
+ mImpl = util::make_unique<PseudoMethodNone>();
+ break;
case Method::kAccent:
- mImpl = util::make_unique<PseudoMethodAccent>();
- break;
+ mImpl = util::make_unique<PseudoMethodAccent>();
+ break;
case Method::kBidi:
- mImpl = util::make_unique<PseudoMethodBidi>();
- break;
- }
+ mImpl = util::make_unique<PseudoMethodBidi>();
+ break;
+ }
}
std::string Pseudolocalizer::text(const StringPiece& text) {
- std::string out;
- size_t depth = mLastDepth;
- size_t lastpos, pos;
- const size_t length = text.size();
- const char* str = text.data();
- bool escaped = false;
- for (lastpos = pos = 0; pos < length; pos++) {
- char16_t c = str[pos];
- if (escaped) {
- escaped = false;
- continue;
- }
- if (c == '\'') {
- escaped = true;
- continue;
- }
-
- if (c == k_arg_start) {
- depth++;
- } else if (c == k_arg_end && depth) {
- depth--;
- }
-
- if (mLastDepth != depth || pos == length - 1) {
- bool pseudo = ((mLastDepth % 2) == 0);
- size_t nextpos = pos;
- if (!pseudo || depth == mLastDepth) {
- nextpos++;
- }
- size_t size = nextpos - lastpos;
- if (size) {
- std::string chunk = text.substr(lastpos, size).toString();
- if (pseudo) {
- chunk = mImpl->text(chunk);
- } else if (str[lastpos] == k_arg_start && str[nextpos - 1] == k_arg_end) {
- chunk = mImpl->placeholder(chunk);
- }
- out.append(chunk);
- }
- if (pseudo && depth < mLastDepth) { // End of message
- out.append(mImpl->end());
- } else if (!pseudo && depth > mLastDepth) { // Start of message
- out.append(mImpl->start());
- }
- lastpos = nextpos;
- mLastDepth = depth;
- }
+ std::string out;
+ size_t depth = mLastDepth;
+ size_t lastpos, pos;
+ const size_t length = text.size();
+ const char* str = text.data();
+ bool escaped = false;
+ for (lastpos = pos = 0; pos < length; pos++) {
+ char16_t c = str[pos];
+ if (escaped) {
+ escaped = false;
+ continue;
}
- return out;
+ if (c == '\'') {
+ escaped = true;
+ continue;
+ }
+
+ if (c == k_arg_start) {
+ depth++;
+ } else if (c == k_arg_end && depth) {
+ depth--;
+ }
+
+ if (mLastDepth != depth || pos == length - 1) {
+ bool pseudo = ((mLastDepth % 2) == 0);
+ size_t nextpos = pos;
+ if (!pseudo || depth == mLastDepth) {
+ nextpos++;
+ }
+ size_t size = nextpos - lastpos;
+ if (size) {
+ std::string chunk = text.substr(lastpos, size).toString();
+ if (pseudo) {
+ chunk = mImpl->text(chunk);
+ } else if (str[lastpos] == k_arg_start &&
+ str[nextpos - 1] == k_arg_end) {
+ chunk = mImpl->placeholder(chunk);
+ }
+ out.append(chunk);
+ }
+ if (pseudo && depth < mLastDepth) { // End of message
+ out.append(mImpl->end());
+ } else if (!pseudo && depth > mLastDepth) { // Start of message
+ out.append(mImpl->start());
+ }
+ lastpos = nextpos;
+ mLastDepth = depth;
+ }
+ }
+ return out;
}
static const char* pseudolocalizeChar(const char c) {
- switch (c) {
- case 'a': return "\u00e5";
- case 'b': return "\u0253";
- case 'c': return "\u00e7";
- case 'd': return "\u00f0";
- case 'e': return "\u00e9";
- case 'f': return "\u0192";
- case 'g': return "\u011d";
- case 'h': return "\u0125";
- case 'i': return "\u00ee";
- case 'j': return "\u0135";
- case 'k': return "\u0137";
- case 'l': return "\u013c";
- case 'm': return "\u1e3f";
- case 'n': return "\u00f1";
- case 'o': return "\u00f6";
- case 'p': return "\u00fe";
- case 'q': return "\u0051";
- case 'r': return "\u0155";
- case 's': return "\u0161";
- case 't': return "\u0163";
- case 'u': return "\u00fb";
- case 'v': return "\u0056";
- case 'w': return "\u0175";
- case 'x': return "\u0445";
- case 'y': return "\u00fd";
- case 'z': return "\u017e";
- case 'A': return "\u00c5";
- case 'B': return "\u03b2";
- case 'C': return "\u00c7";
- case 'D': return "\u00d0";
- case 'E': return "\u00c9";
- case 'G': return "\u011c";
- case 'H': return "\u0124";
- case 'I': return "\u00ce";
- case 'J': return "\u0134";
- case 'K': return "\u0136";
- case 'L': return "\u013b";
- case 'M': return "\u1e3e";
- case 'N': return "\u00d1";
- case 'O': return "\u00d6";
- case 'P': return "\u00de";
- case 'Q': return "\u0071";
- case 'R': return "\u0154";
- case 'S': return "\u0160";
- case 'T': return "\u0162";
- case 'U': return "\u00db";
- case 'V': return "\u03bd";
- case 'W': return "\u0174";
- case 'X': return "\u00d7";
- case 'Y': return "\u00dd";
- case 'Z': return "\u017d";
- case '!': return "\u00a1";
- case '?': return "\u00bf";
- case '$': return "\u20ac";
- default: return nullptr;
- }
+ switch (c) {
+ case 'a':
+ return "\u00e5";
+ case 'b':
+ return "\u0253";
+ case 'c':
+ return "\u00e7";
+ case 'd':
+ return "\u00f0";
+ case 'e':
+ return "\u00e9";
+ case 'f':
+ return "\u0192";
+ case 'g':
+ return "\u011d";
+ case 'h':
+ return "\u0125";
+ case 'i':
+ return "\u00ee";
+ case 'j':
+ return "\u0135";
+ case 'k':
+ return "\u0137";
+ case 'l':
+ return "\u013c";
+ case 'm':
+ return "\u1e3f";
+ case 'n':
+ return "\u00f1";
+ case 'o':
+ return "\u00f6";
+ case 'p':
+ return "\u00fe";
+ case 'q':
+ return "\u0051";
+ case 'r':
+ return "\u0155";
+ case 's':
+ return "\u0161";
+ case 't':
+ return "\u0163";
+ case 'u':
+ return "\u00fb";
+ case 'v':
+ return "\u0056";
+ case 'w':
+ return "\u0175";
+ case 'x':
+ return "\u0445";
+ case 'y':
+ return "\u00fd";
+ case 'z':
+ return "\u017e";
+ case 'A':
+ return "\u00c5";
+ case 'B':
+ return "\u03b2";
+ case 'C':
+ return "\u00c7";
+ case 'D':
+ return "\u00d0";
+ case 'E':
+ return "\u00c9";
+ case 'G':
+ return "\u011c";
+ case 'H':
+ return "\u0124";
+ case 'I':
+ return "\u00ce";
+ case 'J':
+ return "\u0134";
+ case 'K':
+ return "\u0136";
+ case 'L':
+ return "\u013b";
+ case 'M':
+ return "\u1e3e";
+ case 'N':
+ return "\u00d1";
+ case 'O':
+ return "\u00d6";
+ case 'P':
+ return "\u00de";
+ case 'Q':
+ return "\u0071";
+ case 'R':
+ return "\u0154";
+ case 'S':
+ return "\u0160";
+ case 'T':
+ return "\u0162";
+ case 'U':
+ return "\u00db";
+ case 'V':
+ return "\u03bd";
+ case 'W':
+ return "\u0174";
+ case 'X':
+ return "\u00d7";
+ case 'Y':
+ return "\u00dd";
+ case 'Z':
+ return "\u017d";
+ case '!':
+ return "\u00a1";
+ case '?':
+ return "\u00bf";
+ case '$':
+ return "\u20ac";
+ default:
+ return nullptr;
+ }
}
static bool isPossibleNormalPlaceholderEnd(const char c) {
- switch (c) {
- case 's': return true;
- case 'S': return true;
- case 'c': return true;
- case 'C': return true;
- case 'd': return true;
- case 'o': return true;
- case 'x': return true;
- case 'X': return true;
- case 'f': return true;
- case 'e': return true;
- case 'E': return true;
- case 'g': return true;
- case 'G': return true;
- case 'a': return true;
- case 'A': return true;
- case 'b': return true;
- case 'B': return true;
- case 'h': return true;
- case 'H': return true;
- case '%': return true;
- case 'n': return true;
- default: return false;
- }
+ switch (c) {
+ case 's':
+ return true;
+ case 'S':
+ return true;
+ case 'c':
+ return true;
+ case 'C':
+ return true;
+ case 'd':
+ return true;
+ case 'o':
+ return true;
+ case 'x':
+ return true;
+ case 'X':
+ return true;
+ case 'f':
+ return true;
+ case 'e':
+ return true;
+ case 'E':
+ return true;
+ case 'g':
+ return true;
+ case 'G':
+ return true;
+ case 'a':
+ return true;
+ case 'A':
+ return true;
+ case 'b':
+ return true;
+ case 'B':
+ return true;
+ case 'h':
+ return true;
+ case 'H':
+ return true;
+ case '%':
+ return true;
+ case 'n':
+ return true;
+ default:
+ return false;
+ }
}
static std::string pseudoGenerateExpansion(const unsigned int length) {
- std::string result = k_expansion_string;
- const char* s = result.data();
- if (result.size() < length) {
- result += " ";
- result += pseudoGenerateExpansion(length - result.size());
- } else {
- int ext = 0;
- // Should contain only whole words, so looking for a space
- for (unsigned int i = length + 1; i < result.size(); ++i) {
- ++ext;
- if (s[i] == ' ') {
- break;
- }
- }
- result = result.substr(0, length + ext);
+ std::string result = k_expansion_string;
+ const char* s = result.data();
+ if (result.size() < length) {
+ result += " ";
+ result += pseudoGenerateExpansion(length - result.size());
+ } else {
+ int ext = 0;
+ // Should contain only whole words, so looking for a space
+ for (unsigned int i = length + 1; i < result.size(); ++i) {
+ ++ext;
+ if (s[i] == ' ') {
+ break;
+ }
}
- return result;
+ result = result.substr(0, length + ext);
+ }
+ return result;
}
std::string PseudoMethodAccent::start() {
- std::string result;
- if (mDepth == 0) {
- result = "[";
- }
- mWordCount = mLength = 0;
- mDepth++;
- return result;
+ std::string result;
+ if (mDepth == 0) {
+ result = "[";
+ }
+ mWordCount = mLength = 0;
+ mDepth++;
+ return result;
}
std::string PseudoMethodAccent::end() {
- std::string result;
- if (mLength) {
- result += " ";
- result += pseudoGenerateExpansion(mWordCount > 3 ? mLength : mLength / 2);
- }
- mWordCount = mLength = 0;
- mDepth--;
- if (mDepth == 0) {
- result += "]";
- }
- return result;
+ std::string result;
+ if (mLength) {
+ result += " ";
+ result += pseudoGenerateExpansion(mWordCount > 3 ? mLength : mLength / 2);
+ }
+ mWordCount = mLength = 0;
+ mDepth--;
+ if (mDepth == 0) {
+ result += "]";
+ }
+ return result;
}
/**
@@ -267,128 +349,125 @@
*
* Note: This leaves placeholder syntax untouched.
*/
-std::string PseudoMethodAccent::text(const StringPiece& source)
-{
- const char* s = source.data();
- std::string result;
- const size_t I = source.size();
- bool lastspace = true;
- for (size_t i = 0; i < I; i++) {
- char c = s[i];
- if (c == '%') {
- // Placeholder syntax, no need to pseudolocalize
- std::string chunk;
- bool end = false;
- chunk.append(&c, 1);
- while (!end && i + 1 < I) {
- ++i;
- c = s[i];
- chunk.append(&c, 1);
- if (isPossibleNormalPlaceholderEnd(c)) {
- end = true;
- } else if (i + 1 < I && c == 't') {
- ++i;
- c = s[i];
- chunk.append(&c, 1);
- end = true;
- }
- }
- // Treat chunk as a placeholder unless it ends with %.
- result += ((c == '%') ? chunk : placeholder(chunk));
- } else if (c == '<' || c == '&') {
- // html syntax, no need to pseudolocalize
- bool tag_closed = false;
- while (!tag_closed && i < I) {
- if (c == '&') {
- std::string escapeText;
- escapeText.append(&c, 1);
- bool end = false;
- size_t htmlCodePos = i;
- while (!end && htmlCodePos < I) {
- ++htmlCodePos;
- c = s[htmlCodePos];
- escapeText.append(&c, 1);
- // Valid html code
- if (c == ';') {
- end = true;
- i = htmlCodePos;
- }
- // Wrong html code
- else if (!((c == '#' ||
- (c >= 'a' && c <= 'z') ||
- (c >= 'A' && c <= 'Z') ||
- (c >= '0' && c <= '9')))) {
- end = true;
- }
- }
- result += escapeText;
- if (escapeText != "<") {
- tag_closed = true;
- }
- continue;
- }
- if (c == '>') {
- tag_closed = true;
- result.append(&c, 1);
- continue;
- }
- result.append(&c, 1);
- i++;
- c = s[i];
- }
- } else {
- // This is a pure text that should be pseudolocalized
- const char* p = pseudolocalizeChar(c);
- if (p != nullptr) {
- result += p;
- } else {
- bool space = isspace(c);
- if (lastspace && !space) {
- mWordCount++;
- }
- lastspace = space;
- result.append(&c, 1);
- }
- // Count only pseudolocalizable chars and delimiters
- mLength++;
+std::string PseudoMethodAccent::text(const StringPiece& source) {
+ const char* s = source.data();
+ std::string result;
+ const size_t I = source.size();
+ bool lastspace = true;
+ for (size_t i = 0; i < I; i++) {
+ char c = s[i];
+ if (c == '%') {
+ // Placeholder syntax, no need to pseudolocalize
+ std::string chunk;
+ bool end = false;
+ chunk.append(&c, 1);
+ while (!end && i + 1 < I) {
+ ++i;
+ c = s[i];
+ chunk.append(&c, 1);
+ if (isPossibleNormalPlaceholderEnd(c)) {
+ end = true;
+ } else if (i + 1 < I && c == 't') {
+ ++i;
+ c = s[i];
+ chunk.append(&c, 1);
+ end = true;
}
- }
- return result;
-}
-
-std::string PseudoMethodAccent::placeholder(const StringPiece& source) {
- // Surround a placeholder with brackets
- return k_placeholder_open + source.toString() + k_placeholder_close;
-}
-
-std::string PseudoMethodBidi::text(const StringPiece& source) {
- const char* s = source.data();
- std::string result;
- bool lastspace = true;
- bool space = true;
- for (size_t i = 0; i < source.size(); i++) {
- char c = s[i];
- space = isspace(c);
+ }
+ // Treat chunk as a placeholder unless it ends with %.
+ result += ((c == '%') ? chunk : placeholder(chunk));
+ } else if (c == '<' || c == '&') {
+ // html syntax, no need to pseudolocalize
+ bool tag_closed = false;
+ while (!tag_closed && i < I) {
+ if (c == '&') {
+ std::string escapeText;
+ escapeText.append(&c, 1);
+ bool end = false;
+ size_t htmlCodePos = i;
+ while (!end && htmlCodePos < I) {
+ ++htmlCodePos;
+ c = s[htmlCodePos];
+ escapeText.append(&c, 1);
+ // Valid html code
+ if (c == ';') {
+ end = true;
+ i = htmlCodePos;
+ }
+ // Wrong html code
+ else if (!((c == '#' || (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')))) {
+ end = true;
+ }
+ }
+ result += escapeText;
+ if (escapeText != "<") {
+ tag_closed = true;
+ }
+ continue;
+ }
+ if (c == '>') {
+ tag_closed = true;
+ result.append(&c, 1);
+ continue;
+ }
+ result.append(&c, 1);
+ i++;
+ c = s[i];
+ }
+ } else {
+ // This is a pure text that should be pseudolocalized
+ const char* p = pseudolocalizeChar(c);
+ if (p != nullptr) {
+ result += p;
+ } else {
+ bool space = isspace(c);
if (lastspace && !space) {
- // Word start
- result += k_rlm + k_rlo;
- } else if (!lastspace && space) {
- // Word end
- result += k_pdf + k_rlm;
+ mWordCount++;
}
lastspace = space;
result.append(&c, 1);
+ }
+ // Count only pseudolocalizable chars and delimiters
+ mLength++;
}
- if (!lastspace) {
- // End of last word
- result += k_pdf + k_rlm;
+ }
+ return result;
+}
+
+std::string PseudoMethodAccent::placeholder(const StringPiece& source) {
+ // Surround a placeholder with brackets
+ return k_placeholder_open + source.toString() + k_placeholder_close;
+}
+
+std::string PseudoMethodBidi::text(const StringPiece& source) {
+ const char* s = source.data();
+ std::string result;
+ bool lastspace = true;
+ bool space = true;
+ for (size_t i = 0; i < source.size(); i++) {
+ char c = s[i];
+ space = isspace(c);
+ if (lastspace && !space) {
+ // Word start
+ result += k_rlm + k_rlo;
+ } else if (!lastspace && space) {
+ // Word end
+ result += k_pdf + k_rlm;
}
- return result;
+ lastspace = space;
+ result.append(&c, 1);
+ }
+ if (!lastspace) {
+ // End of last word
+ result += k_pdf + k_rlm;
+ }
+ return result;
}
std::string PseudoMethodBidi::placeholder(const StringPiece& source) {
- // Surround a placeholder with directionality change sequence
- return k_rlm + k_rlo + source.toString() + k_pdf + k_rlm;
+ // Surround a placeholder with directionality change sequence
+ return k_rlm + k_rlo + source.toString() + k_pdf + k_rlm;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h
index 91d17d1..a526877 100644
--- a/tools/aapt2/compile/Pseudolocalizer.h
+++ b/tools/aapt2/compile/Pseudolocalizer.h
@@ -27,32 +27,33 @@
namespace aapt {
class PseudoMethodImpl {
-public:
- virtual ~PseudoMethodImpl() {}
- virtual std::string start() { return {}; }
- virtual std::string end() { return {}; }
- virtual std::string text(const StringPiece& text) = 0;
- virtual std::string placeholder(const StringPiece& text) = 0;
+ public:
+ virtual ~PseudoMethodImpl() {}
+ virtual std::string start() { return {}; }
+ virtual std::string end() { return {}; }
+ virtual std::string text(const StringPiece& text) = 0;
+ virtual std::string placeholder(const StringPiece& text) = 0;
};
class Pseudolocalizer {
-public:
- enum class Method {
- kNone,
- kAccent,
- kBidi,
- };
+ public:
+ enum class Method {
+ kNone,
+ kAccent,
+ kBidi,
+ };
- explicit Pseudolocalizer(Method method);
- void setMethod(Method method);
- std::string start() { return mImpl->start(); }
- std::string end() { return mImpl->end(); }
- std::string text(const StringPiece& text);
-private:
- std::unique_ptr<PseudoMethodImpl> mImpl;
- size_t mLastDepth;
+ explicit Pseudolocalizer(Method method);
+ void setMethod(Method method);
+ std::string start() { return mImpl->start(); }
+ std::string end() { return mImpl->end(); }
+ std::string text(const StringPiece& text);
+
+ private:
+ std::unique_ptr<PseudoMethodImpl> mImpl;
+ size_t mLastDepth;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_COMPILE_PSEUDOLOCALIZE_H */
diff --git a/tools/aapt2/compile/Pseudolocalizer_test.cpp b/tools/aapt2/compile/Pseudolocalizer_test.cpp
index c33e152..a152ed6 100644
--- a/tools/aapt2/compile/Pseudolocalizer_test.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer_test.cpp
@@ -25,199 +25,207 @@
// In this context, 'Axis' represents a particular field in the configuration,
// such as language or density.
-static ::testing::AssertionResult simpleHelper(const char* input, const char* expected,
+static ::testing::AssertionResult simpleHelper(const char* input,
+ const char* expected,
Pseudolocalizer::Method method) {
- Pseudolocalizer pseudo(method);
- std::string result = pseudo.start() + pseudo.text(input) + pseudo.end();
- if (result != expected) {
- return ::testing::AssertionFailure() << expected << " != " << result;
- }
- return ::testing::AssertionSuccess();
+ Pseudolocalizer pseudo(method);
+ std::string result = pseudo.start() + pseudo.text(input) + pseudo.end();
+ if (result != expected) {
+ return ::testing::AssertionFailure() << expected << " != " << result;
+ }
+ return ::testing::AssertionSuccess();
}
-static ::testing::AssertionResult compoundHelper(const char* in1, const char* in2, const char *in3,
- const char* expected,
- Pseudolocalizer::Method method) {
- Pseudolocalizer pseudo(method);
- std::string result = pseudo.start() + pseudo.text(in1) + pseudo.text(in2) + pseudo.text(in3) +
- pseudo.end();
- if (result != expected) {
- return ::testing::AssertionFailure() << expected << " != " << result;
- }
- return ::testing::AssertionSuccess();
+static ::testing::AssertionResult compoundHelper(
+ const char* in1, const char* in2, const char* in3, const char* expected,
+ Pseudolocalizer::Method method) {
+ Pseudolocalizer pseudo(method);
+ std::string result = pseudo.start() + pseudo.text(in1) + pseudo.text(in2) +
+ pseudo.text(in3) + pseudo.end();
+ if (result != expected) {
+ return ::testing::AssertionFailure() << expected << " != " << result;
+ }
+ return ::testing::AssertionSuccess();
}
TEST(PseudolocalizerTest, NoPseudolocalization) {
- EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kNone));
- EXPECT_TRUE(simpleHelper("Hello, world", "Hello, world", Pseudolocalizer::Method::kNone));
+ EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kNone));
+ EXPECT_TRUE(simpleHelper("Hello, world", "Hello, world",
+ Pseudolocalizer::Method::kNone));
- EXPECT_TRUE(compoundHelper("Hello,", " world", "",
- "Hello, world", Pseudolocalizer::Method::kNone));
+ EXPECT_TRUE(compoundHelper("Hello,", " world", "", "Hello, world",
+ Pseudolocalizer::Method::kNone));
}
TEST(PseudolocalizerTest, PlaintextAccent) {
- EXPECT_TRUE(simpleHelper("", "[]", Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(simpleHelper("Hello, world",
- "[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("", "[]", Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("Hello, world", "[Ĥéļļö, ŵöŕļð one two]",
+ Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(simpleHelper("Hello, %1d",
- "[Ĥéļļö, »%1d« one two]", Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("Hello, %1d", "[Ĥéļļö, »%1d« one two]",
+ Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(simpleHelper("Battery %1d%%",
- "[βåţţéŕý »%1d«%% one two]", Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(simpleHelper("^1 %", "[^1 % one]", Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(compoundHelper("", "", "", "[]", Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(compoundHelper("Hello,", " world", "",
- "[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("Battery %1d%%", "[βåţţéŕý »%1d«%% one two]",
+ Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(
+ simpleHelper("^1 %", "[^1 % one]", Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(
+ compoundHelper("", "", "", "[]", Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(compoundHelper("Hello,", " world", "", "[Ĥéļļö, ŵöŕļð one two]",
+ Pseudolocalizer::Method::kAccent));
}
TEST(PseudolocalizerTest, PlaintextBidi) {
- EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kBidi));
- EXPECT_TRUE(simpleHelper("word",
- "\xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f",
- Pseudolocalizer::Method::kBidi));
- EXPECT_TRUE(simpleHelper(" word ",
- " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ",
- Pseudolocalizer::Method::kBidi));
- EXPECT_TRUE(simpleHelper(" word ",
- " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ",
- Pseudolocalizer::Method::kBidi));
- EXPECT_TRUE(simpleHelper("hello\n world\n",
- "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \
- " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n",
- Pseudolocalizer::Method::kBidi));
- EXPECT_TRUE(compoundHelper("hello", "\n ", " world\n",
- "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \
- " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n",
- Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(simpleHelper(
+ "word", "\xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f",
+ Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(simpleHelper(
+ " word ", " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ",
+ Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(simpleHelper(
+ " word ", " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ",
+ Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(
+ simpleHelper("hello\n world\n",
+ "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n"
+ " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n",
+ Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(compoundHelper(
+ "hello", "\n ", " world\n",
+ "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n"
+ " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n",
+ Pseudolocalizer::Method::kBidi));
}
TEST(PseudolocalizerTest, SimpleICU) {
- // Single-fragment messages
- EXPECT_TRUE(simpleHelper("{placeholder}", "[»{placeholder}«]",
+ // Single-fragment messages
+ EXPECT_TRUE(simpleHelper("{placeholder}", "[»{placeholder}«]",
+ Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("{USER} is offline", "[»{USER}« îš öƒƒļîñé one two]",
+ Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("Copy from {path1} to {path2}",
+ "[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]",
+ Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("Today is {1,date} {1,time}",
+ "[Ţöðåý îš »{1,date}« »{1,time}« one two]",
+ Pseudolocalizer::Method::kAccent));
+
+ // Multi-fragment messages
+ EXPECT_TRUE(compoundHelper("{USER}", " ", "is offline",
+ "[»{USER}« îš öƒƒļîñé one two]",
Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(simpleHelper("{USER} is offline",
- "[»{USER}« îš öƒƒļîñé one two]", Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(simpleHelper("Copy from {path1} to {path2}",
+ EXPECT_TRUE(compoundHelper("Copy from ", "{path1}", " to {path2}",
"[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]",
Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(simpleHelper("Today is {1,date} {1,time}",
- "[Ţöðåý îš »{1,date}« »{1,time}« one two]",
- Pseudolocalizer::Method::kAccent));
-
- // Multi-fragment messages
- EXPECT_TRUE(compoundHelper("{USER}", " ", "is offline",
- "[»{USER}« îš öƒƒļîñé one two]",
- Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(compoundHelper("Copy from ", "{path1}", " to {path2}",
- "[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]",
- Pseudolocalizer::Method::kAccent));
}
TEST(PseudolocalizerTest, ICUBidi) {
- // Single-fragment messages
- EXPECT_TRUE(simpleHelper("{placeholder}",
- "\xe2\x80\x8f\xE2\x80\xae{placeholder}\xE2\x80\xac\xe2\x80\x8f",
- Pseudolocalizer::Method::kBidi));
- EXPECT_TRUE(simpleHelper(
- "{COUNT, plural, one {one} other {other}}",
- "{COUNT, plural, " \
- "one {\xe2\x80\x8f\xE2\x80\xaeone\xE2\x80\xac\xe2\x80\x8f} " \
- "other {\xe2\x80\x8f\xE2\x80\xaeother\xE2\x80\xac\xe2\x80\x8f}}",
- Pseudolocalizer::Method::kBidi));
+ // Single-fragment messages
+ EXPECT_TRUE(simpleHelper(
+ "{placeholder}",
+ "\xe2\x80\x8f\xE2\x80\xae{placeholder}\xE2\x80\xac\xe2\x80\x8f",
+ Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(simpleHelper(
+ "{COUNT, plural, one {one} other {other}}",
+ "{COUNT, plural, "
+ "one {\xe2\x80\x8f\xE2\x80\xaeone\xE2\x80\xac\xe2\x80\x8f} "
+ "other {\xe2\x80\x8f\xE2\x80\xaeother\xE2\x80\xac\xe2\x80\x8f}}",
+ Pseudolocalizer::Method::kBidi));
}
TEST(PseudolocalizerTest, Escaping) {
- // Single-fragment messages
- EXPECT_TRUE(simpleHelper("'{USER'} is offline",
- "['{ÛŠÉŔ'} îš öƒƒļîñé one two three]",
- Pseudolocalizer::Method::kAccent));
+ // Single-fragment messages
+ EXPECT_TRUE(simpleHelper("'{USER'} is offline",
+ "['{ÛŠÉŔ'} îš öƒƒļîñé one two three]",
+ Pseudolocalizer::Method::kAccent));
- // Multi-fragment messages
- EXPECT_TRUE(compoundHelper("'{USER}", " ", "''is offline",
- "['{ÛŠÉŔ} ''îš öƒƒļîñé one two three]",
- Pseudolocalizer::Method::kAccent));
+ // Multi-fragment messages
+ EXPECT_TRUE(compoundHelper("'{USER}", " ", "''is offline",
+ "['{ÛŠÉŔ} ''îš öƒƒļîñé one two three]",
+ Pseudolocalizer::Method::kAccent));
}
TEST(PseudolocalizerTest, PluralsAndSelects) {
- EXPECT_TRUE(simpleHelper(
- "{COUNT, plural, one {Delete a file} other {Delete {COUNT} files}}",
- "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " \
+ EXPECT_TRUE(simpleHelper(
+ "{COUNT, plural, one {Delete a file} other {Delete {COUNT} files}}",
+ "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} "
+ "other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]",
+ Pseudolocalizer::Method::kAccent));
+
+ EXPECT_TRUE(
+ simpleHelper("Distance is {COUNT, plural, one {# mile} other {# miles}}",
+ "[Ðîšţåñçé îš {COUNT, plural, one {# ḿîļé one two} "
+ "other {# ḿîļéš one two}}]",
+ Pseudolocalizer::Method::kAccent));
+
+ EXPECT_TRUE(simpleHelper(
+ "{1, select, female {{1} added you} "
+ "male {{1} added you} other {{1} added you}}",
+ "[{1, select, female {»{1}« åððéð ýöû one two} "
+ "male {»{1}« åððéð ýöû one two} other {»{1}« åððéð ýöû one two}}]",
+ Pseudolocalizer::Method::kAccent));
+
+ EXPECT_TRUE(
+ compoundHelper("{COUNT, plural, one {Delete a file} "
+ "other {Delete ",
+ "{COUNT}", " files}}",
+ "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} "
"other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]",
- Pseudolocalizer::Method::kAccent));
-
- EXPECT_TRUE(simpleHelper(
- "Distance is {COUNT, plural, one {# mile} other {# miles}}",
- "[Ðîšţåñçé îš {COUNT, plural, one {# ḿîļé one two} " \
- "other {# ḿîļéš one two}}]",
- Pseudolocalizer::Method::kAccent));
-
- EXPECT_TRUE(simpleHelper(
- "{1, select, female {{1} added you} " \
- "male {{1} added you} other {{1} added you}}",
- "[{1, select, female {»{1}« åððéð ýöû one two} " \
- "male {»{1}« åððéð ýöû one two} other {»{1}« åððéð ýöû one two}}]",
- Pseudolocalizer::Method::kAccent));
-
- EXPECT_TRUE(compoundHelper(
- "{COUNT, plural, one {Delete a file} " \
- "other {Delete ", "{COUNT}", " files}}",
- "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " \
- "other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]",
- Pseudolocalizer::Method::kAccent));
+ Pseudolocalizer::Method::kAccent));
}
TEST(PseudolocalizerTest, NestedICU) {
- EXPECT_TRUE(simpleHelper(
- "{person, select, " \
- "female {" \
- "{num_circles, plural," \
- "=0{{person} didn't add you to any of her circles.}" \
- "=1{{person} added you to one of her circles.}" \
- "other{{person} added you to her # circles.}}}" \
- "male {" \
- "{num_circles, plural," \
- "=0{{person} didn't add you to any of his circles.}" \
- "=1{{person} added you to one of his circles.}" \
- "other{{person} added you to his # circles.}}}" \
- "other {" \
- "{num_circles, plural," \
- "=0{{person} didn't add you to any of their circles.}" \
- "=1{{person} added you to one of their circles.}" \
- "other{{person} added you to their # circles.}}}}",
- "[{person, select, " \
- "female {" \
- "{num_circles, plural," \
- "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥéŕ çîŕçļéš." \
- " one two three four five}" \
- "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥéŕ çîŕçļéš." \
- " one two three four}" \
- "other{»{person}« åððéð ýöû ţö ĥéŕ # çîŕçļéš." \
- " one two three four}}}" \
- "male {" \
- "{num_circles, plural," \
- "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥîš çîŕçļéš." \
- " one two three four five}" \
- "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥîš çîŕçļéš." \
- " one two three four}" \
- "other{»{person}« åððéð ýöû ţö ĥîš # çîŕçļéš." \
- " one two three four}}}" \
- "other {{num_circles, plural," \
- "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ţĥéîŕ çîŕçļéš." \
- " one two three four five}" \
- "=1{»{person}« åððéð ýöû ţö öñé öƒ ţĥéîŕ çîŕçļéš." \
- " one two three four}" \
- "other{»{person}« åððéð ýöû ţö ţĥéîŕ # çîŕçļéš." \
- " one two three four}}}}]",
- Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(
+ simpleHelper("{person, select, "
+ "female {"
+ "{num_circles, plural,"
+ "=0{{person} didn't add you to any of her circles.}"
+ "=1{{person} added you to one of her circles.}"
+ "other{{person} added you to her # circles.}}}"
+ "male {"
+ "{num_circles, plural,"
+ "=0{{person} didn't add you to any of his circles.}"
+ "=1{{person} added you to one of his circles.}"
+ "other{{person} added you to his # circles.}}}"
+ "other {"
+ "{num_circles, plural,"
+ "=0{{person} didn't add you to any of their circles.}"
+ "=1{{person} added you to one of their circles.}"
+ "other{{person} added you to their # circles.}}}}",
+ "[{person, select, "
+ "female {"
+ "{num_circles, plural,"
+ "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥéŕ çîŕçļéš."
+ " one two three four five}"
+ "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥéŕ çîŕçļéš."
+ " one two three four}"
+ "other{»{person}« åððéð ýöû ţö ĥéŕ # çîŕçļéš."
+ " one two three four}}}"
+ "male {"
+ "{num_circles, plural,"
+ "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥîš çîŕçļéš."
+ " one two three four five}"
+ "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥîš çîŕçļéš."
+ " one two three four}"
+ "other{»{person}« åððéð ýöû ţö ĥîš # çîŕçļéš."
+ " one two three four}}}"
+ "other {{num_circles, plural,"
+ "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ţĥéîŕ çîŕçļéš."
+ " one two three four five}"
+ "=1{»{person}« åððéð ýöû ţö öñé öƒ ţĥéîŕ çîŕçļéš."
+ " one two three four}"
+ "other{»{person}« åððéð ýöû ţö ţĥéîŕ # çîŕçļéš."
+ " one two three four}}}}]",
+ Pseudolocalizer::Method::kAccent));
}
TEST(PseudolocalizerTest, RedefineMethod) {
- Pseudolocalizer pseudo(Pseudolocalizer::Method::kAccent);
- std::string result = pseudo.text("Hello, ");
- pseudo.setMethod(Pseudolocalizer::Method::kNone);
- result += pseudo.text("world!");
- ASSERT_EQ(StringPiece("Ĥéļļö, world!"), result);
+ Pseudolocalizer pseudo(Pseudolocalizer::Method::kAccent);
+ std::string result = pseudo.text("Hello, ");
+ pseudo.setMethod(Pseudolocalizer::Method::kNone);
+ result += pseudo.text("world!");
+ ASSERT_EQ(StringPiece("Ĥéļļö, world!"), result);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp
index 3901419..aa8b1df 100644
--- a/tools/aapt2/compile/XmlIdCollector.cpp
+++ b/tools/aapt2/compile/XmlIdCollector.cpp
@@ -14,9 +14,9 @@
* limitations under the License.
*/
+#include "compile/XmlIdCollector.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
-#include "compile/XmlIdCollector.h"
#include "xml/XmlDom.h"
#include <algorithm>
@@ -27,44 +27,44 @@
namespace {
static bool cmpName(const SourcedResourceName& a, const ResourceNameRef& b) {
- return a.name < b;
+ return a.name < b;
}
struct IdCollector : public xml::Visitor {
- using xml::Visitor::visit;
+ using xml::Visitor::visit;
- std::vector<SourcedResourceName>* mOutSymbols;
+ std::vector<SourcedResourceName>* mOutSymbols;
- explicit IdCollector(std::vector<SourcedResourceName>* outSymbols) : mOutSymbols(outSymbols) {
- }
+ explicit IdCollector(std::vector<SourcedResourceName>* outSymbols)
+ : mOutSymbols(outSymbols) {}
- void visit(xml::Element* element) override {
- for (xml::Attribute& attr : element->attributes) {
- ResourceNameRef name;
- bool create = false;
- if (ResourceUtils::parseReference(attr.value, &name, &create, nullptr)) {
- if (create && name.type == ResourceType::kId) {
- auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(),
- name, cmpName);
- if (iter == mOutSymbols->end() || iter->name != name) {
- mOutSymbols->insert(iter, SourcedResourceName{ name.toResourceName(),
- element->lineNumber });
- }
- }
- }
+ void visit(xml::Element* element) override {
+ for (xml::Attribute& attr : element->attributes) {
+ ResourceNameRef name;
+ bool create = false;
+ if (ResourceUtils::parseReference(attr.value, &name, &create, nullptr)) {
+ if (create && name.type == ResourceType::kId) {
+ auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(),
+ name, cmpName);
+ if (iter == mOutSymbols->end() || iter->name != name) {
+ mOutSymbols->insert(iter, SourcedResourceName{name.toResourceName(),
+ element->lineNumber});
+ }
}
-
- xml::Visitor::visit(element);
+ }
}
+
+ xml::Visitor::visit(element);
+ }
};
-} // namespace
+} // namespace
bool XmlIdCollector::consume(IAaptContext* context, xml::XmlResource* xmlRes) {
- xmlRes->file.exportedSymbols.clear();
- IdCollector collector(&xmlRes->file.exportedSymbols);
- xmlRes->root->accept(&collector);
- return true;
+ xmlRes->file.exportedSymbols.clear();
+ IdCollector collector(&xmlRes->file.exportedSymbols);
+ xmlRes->root->accept(&collector);
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/XmlIdCollector.h b/tools/aapt2/compile/XmlIdCollector.h
index 1b14944..8423f48 100644
--- a/tools/aapt2/compile/XmlIdCollector.h
+++ b/tools/aapt2/compile/XmlIdCollector.h
@@ -23,9 +23,9 @@
namespace aapt {
struct XmlIdCollector : public IXmlResourceConsumer {
- bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override;
+ bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_XMLIDCOLLECTOR_H */
diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp
index 2c9eab8..08ca7b1 100644
--- a/tools/aapt2/compile/XmlIdCollector_test.cpp
+++ b/tools/aapt2/compile/XmlIdCollector_test.cpp
@@ -18,15 +18,15 @@
#include "test/Builders.h"
#include "test/Context.h"
-#include <algorithm>
#include <gtest/gtest.h>
+#include <algorithm>
namespace aapt {
TEST(XmlIdCollectorTest, CollectsIds) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/foo"
text="@+id/bar">
@@ -34,28 +34,35 @@
class="@+id/bar"/>
</View>)EOF");
- XmlIdCollector collector;
- ASSERT_TRUE(collector.consume(context.get(), doc.get()));
+ XmlIdCollector collector;
+ ASSERT_TRUE(collector.consume(context.get(), doc.get()));
- EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
- SourcedResourceName{ test::parseNameOrDie("id/foo"), 3u }));
+ EXPECT_EQ(
+ 1, std::count(doc->file.exportedSymbols.begin(),
+ doc->file.exportedSymbols.end(),
+ SourcedResourceName{test::parseNameOrDie("id/foo"), 3u}));
- EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
- SourcedResourceName{ test::parseNameOrDie("id/bar"), 3u }));
+ EXPECT_EQ(
+ 1, std::count(doc->file.exportedSymbols.begin(),
+ doc->file.exportedSymbols.end(),
+ SourcedResourceName{test::parseNameOrDie("id/bar"), 3u}));
- EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
- SourcedResourceName{ test::parseNameOrDie("id/car"), 6u }));
+ EXPECT_EQ(
+ 1, std::count(doc->file.exportedSymbols.begin(),
+ doc->file.exportedSymbols.end(),
+ SourcedResourceName{test::parseNameOrDie("id/car"), 6u}));
}
TEST(XmlIdCollectorTest, DontCollectNonIds) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
+ std::unique_ptr<xml::XmlResource> doc =
+ test::buildXmlDom("<View foo=\"@+string/foo\"/>");
- XmlIdCollector collector;
- ASSERT_TRUE(collector.consume(context.get(), doc.get()));
+ XmlIdCollector collector;
+ ASSERT_TRUE(collector.consume(context.get(), doc.get()));
- EXPECT_TRUE(doc->file.exportedSymbols.empty());
+ EXPECT_TRUE(doc->file.exportedSymbols.empty());
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/diff/Diff.cpp b/tools/aapt2/diff/Diff.cpp
index 9b1f057..01f4539 100644
--- a/tools/aapt2/diff/Diff.cpp
+++ b/tools/aapt2/diff/Diff.cpp
@@ -27,412 +27,401 @@
namespace aapt {
class DiffContext : public IAaptContext {
-public:
- const std::string& getCompilationPackage() override {
- return mEmpty;
- }
+ public:
+ const std::string& getCompilationPackage() override { return mEmpty; }
- uint8_t getPackageId() override {
- return 0x0;
- }
+ uint8_t getPackageId() override { return 0x0; }
- IDiagnostics* getDiagnostics() override {
- return &mDiagnostics;
- }
+ IDiagnostics* getDiagnostics() override { return &mDiagnostics; }
- NameMangler* getNameMangler() override {
- return &mNameMangler;
- }
+ NameMangler* getNameMangler() override { return &mNameMangler; }
- SymbolTable* getExternalSymbols() override {
- return &mSymbolTable;
- }
+ SymbolTable* getExternalSymbols() override { return &mSymbolTable; }
- bool verbose() override {
- return false;
- }
+ bool verbose() override { return false; }
- int getMinSdkVersion() override {
- return 0;
- }
+ int getMinSdkVersion() override { return 0; }
-private:
- std::string mEmpty;
- StdErrDiagnostics mDiagnostics;
- NameMangler mNameMangler = NameMangler(NameManglerPolicy{});
- SymbolTable mSymbolTable;
+ private:
+ std::string mEmpty;
+ StdErrDiagnostics mDiagnostics;
+ NameMangler mNameMangler = NameMangler(NameManglerPolicy{});
+ SymbolTable mSymbolTable;
};
class LoadedApk {
-public:
- LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
- std::unique_ptr<ResourceTable> table) :
- mSource(source), mApk(std::move(apk)), mTable(std::move(table)) {
- }
+ public:
+ LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
+ std::unique_ptr<ResourceTable> table)
+ : mSource(source), mApk(std::move(apk)), mTable(std::move(table)) {}
- io::IFileCollection* getFileCollection() {
- return mApk.get();
- }
+ io::IFileCollection* getFileCollection() { return mApk.get(); }
- ResourceTable* getResourceTable() {
- return mTable.get();
- }
+ ResourceTable* getResourceTable() { return mTable.get(); }
- const Source& getSource() {
- return mSource;
- }
+ const Source& getSource() { return mSource; }
-private:
- Source mSource;
- std::unique_ptr<io::IFileCollection> mApk;
- std::unique_ptr<ResourceTable> mTable;
+ private:
+ Source mSource;
+ std::unique_ptr<io::IFileCollection> mApk;
+ std::unique_ptr<ResourceTable> mTable;
- DISALLOW_COPY_AND_ASSIGN(LoadedApk);
+ DISALLOW_COPY_AND_ASSIGN(LoadedApk);
};
-static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context, const StringPiece& path) {
- Source source(path);
- std::string error;
- std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::create(path, &error);
- if (!apk) {
- context->getDiagnostics()->error(DiagMessage(source) << error);
- return {};
- }
+static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context,
+ const StringPiece& path) {
+ Source source(path);
+ std::string error;
+ std::unique_ptr<io::ZipFileCollection> apk =
+ io::ZipFileCollection::create(path, &error);
+ if (!apk) {
+ context->getDiagnostics()->error(DiagMessage(source) << error);
+ return {};
+ }
- io::IFile* file = apk->findFile("resources.arsc");
- if (!file) {
- context->getDiagnostics()->error(DiagMessage(source) << "no resources.arsc found");
- return {};
- }
+ io::IFile* file = apk->findFile("resources.arsc");
+ if (!file) {
+ context->getDiagnostics()->error(DiagMessage(source)
+ << "no resources.arsc found");
+ return {};
+ }
- std::unique_ptr<io::IData> data = file->openAsData();
- if (!data) {
- context->getDiagnostics()->error(DiagMessage(source) << "could not open resources.arsc");
- return {};
- }
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ context->getDiagnostics()->error(DiagMessage(source)
+ << "could not open resources.arsc");
+ return {};
+ }
- std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
- BinaryResourceParser parser(context, table.get(), source, data->data(), data->size());
- if (!parser.parse()) {
- return {};
- }
+ std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+ BinaryResourceParser parser(context, table.get(), source, data->data(),
+ data->size());
+ if (!parser.parse()) {
+ return {};
+ }
- return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
+ return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
}
static void emitDiffLine(const Source& source, const StringPiece& message) {
- std::cerr << source << ": " << message << "\n";
+ std::cerr << source << ": " << message << "\n";
}
-static bool isSymbolVisibilityDifferent(const Symbol& symbolA, const Symbol& symbolB) {
- return symbolA.state != symbolB.state;
+static bool isSymbolVisibilityDifferent(const Symbol& symbolA,
+ const Symbol& symbolB) {
+ return symbolA.state != symbolB.state;
}
template <typename Id>
static bool isIdDiff(const Symbol& symbolA, const Maybe<Id>& idA,
const Symbol& symbolB, const Maybe<Id>& idB) {
- if (symbolA.state == SymbolState::kPublic || symbolB.state == SymbolState::kPublic) {
- return idA != idB;
- }
- return false;
+ if (symbolA.state == SymbolState::kPublic ||
+ symbolB.state == SymbolState::kPublic) {
+ return idA != idB;
+ }
+ return false;
}
-static bool emitResourceConfigValueDiff(IAaptContext* context,
- LoadedApk* apkA,
- ResourceTablePackage* pkgA,
- ResourceTableType* typeA,
- ResourceEntry* entryA,
- ResourceConfigValue* configValueA,
- LoadedApk* apkB,
- ResourceTablePackage* pkgB,
- ResourceTableType* typeB,
- ResourceEntry* entryB,
- ResourceConfigValue* configValueB) {
- Value* valueA = configValueA->value.get();
- Value* valueB = configValueB->value.get();
- if (!valueA->equals(valueB)) {
- std::stringstream strStream;
- strStream << "value " << pkgA->name << ":" << typeA->type << "/" << entryA->name
- << " config=" << configValueA->config << " does not match:\n";
- valueA->print(&strStream);
- strStream << "\n vs \n";
- valueB->print(&strStream);
- emitDiffLine(apkB->getSource(), strStream.str());
- return true;
- }
- return false;
+static bool emitResourceConfigValueDiff(
+ IAaptContext* context, LoadedApk* apkA, ResourceTablePackage* pkgA,
+ ResourceTableType* typeA, ResourceEntry* entryA,
+ ResourceConfigValue* configValueA, LoadedApk* apkB,
+ ResourceTablePackage* pkgB, ResourceTableType* typeB, ResourceEntry* entryB,
+ ResourceConfigValue* configValueB) {
+ Value* valueA = configValueA->value.get();
+ Value* valueB = configValueB->value.get();
+ if (!valueA->equals(valueB)) {
+ std::stringstream strStream;
+ strStream << "value " << pkgA->name << ":" << typeA->type << "/"
+ << entryA->name << " config=" << configValueA->config
+ << " does not match:\n";
+ valueA->print(&strStream);
+ strStream << "\n vs \n";
+ valueB->print(&strStream);
+ emitDiffLine(apkB->getSource(), strStream.str());
+ return true;
+ }
+ return false;
}
-static bool emitResourceEntryDiff(IAaptContext* context,
- LoadedApk* apkA,
+static bool emitResourceEntryDiff(IAaptContext* context, LoadedApk* apkA,
ResourceTablePackage* pkgA,
ResourceTableType* typeA,
- ResourceEntry* entryA,
- LoadedApk* apkB,
+ ResourceEntry* entryA, LoadedApk* apkB,
ResourceTablePackage* pkgB,
ResourceTableType* typeB,
ResourceEntry* entryB) {
- bool diff = false;
- for (std::unique_ptr<ResourceConfigValue>& configValueA : entryA->values) {
- ResourceConfigValue* configValueB = entryB->findValue(configValueA->config);
- if (!configValueB) {
- std::stringstream strStream;
- strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name
- << " config=" << configValueA->config;
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- } else {
- diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA,
- configValueA.get(), apkB, pkgB, typeB, entryB,
- configValueB);
- }
+ bool diff = false;
+ for (std::unique_ptr<ResourceConfigValue>& configValueA : entryA->values) {
+ ResourceConfigValue* configValueB = entryB->findValue(configValueA->config);
+ if (!configValueB) {
+ std::stringstream strStream;
+ strStream << "missing " << pkgA->name << ":" << typeA->type << "/"
+ << entryA->name << " config=" << configValueA->config;
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ } else {
+ diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA,
+ configValueA.get(), apkB, pkgB, typeB,
+ entryB, configValueB);
}
+ }
- // Check for any newly added config values.
- for (std::unique_ptr<ResourceConfigValue>& configValueB : entryB->values) {
- ResourceConfigValue* configValueA = entryA->findValue(configValueB->config);
- if (!configValueA) {
- std::stringstream strStream;
- strStream << "new config " << pkgB->name << ":" << typeB->type << "/" << entryB->name
- << " config=" << configValueB->config;
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- }
+ // Check for any newly added config values.
+ for (std::unique_ptr<ResourceConfigValue>& configValueB : entryB->values) {
+ ResourceConfigValue* configValueA = entryA->findValue(configValueB->config);
+ if (!configValueA) {
+ std::stringstream strStream;
+ strStream << "new config " << pkgB->name << ":" << typeB->type << "/"
+ << entryB->name << " config=" << configValueB->config;
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
}
- return false;
+ }
+ return false;
}
-static bool emitResourceTypeDiff(IAaptContext* context,
- LoadedApk* apkA,
+static bool emitResourceTypeDiff(IAaptContext* context, LoadedApk* apkA,
ResourceTablePackage* pkgA,
- ResourceTableType* typeA,
- LoadedApk* apkB,
+ ResourceTableType* typeA, LoadedApk* apkB,
ResourceTablePackage* pkgB,
ResourceTableType* typeB) {
- bool diff = false;
- for (std::unique_ptr<ResourceEntry>& entryA : typeA->entries) {
- ResourceEntry* entryB = typeB->findEntry(entryA->name);
- if (!entryB) {
- std::stringstream strStream;
- strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name;
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
+ bool diff = false;
+ for (std::unique_ptr<ResourceEntry>& entryA : typeA->entries) {
+ ResourceEntry* entryB = typeB->findEntry(entryA->name);
+ if (!entryB) {
+ std::stringstream strStream;
+ strStream << "missing " << pkgA->name << ":" << typeA->type << "/"
+ << entryA->name;
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ } else {
+ if (isSymbolVisibilityDifferent(entryA->symbolStatus,
+ entryB->symbolStatus)) {
+ std::stringstream strStream;
+ strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
+ << " has different visibility (";
+ if (entryB->symbolStatus.state == SymbolState::kPublic) {
+ strStream << "PUBLIC";
} else {
- if (isSymbolVisibilityDifferent(entryA->symbolStatus, entryB->symbolStatus)) {
- std::stringstream strStream;
- strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
- << " has different visibility (";
- if (entryB->symbolStatus.state == SymbolState::kPublic) {
- strStream << "PUBLIC";
- } else {
- strStream << "PRIVATE";
- }
- strStream << " vs ";
- if (entryA->symbolStatus.state == SymbolState::kPublic) {
- strStream << "PUBLIC";
- } else {
- strStream << "PRIVATE";
- }
- strStream << ")";
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- } else if (isIdDiff(entryA->symbolStatus, entryA->id,
- entryB->symbolStatus, entryB->id)) {
- std::stringstream strStream;
- strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
- << " has different public ID (";
- if (entryB->id) {
- strStream << "0x" << std::hex << entryB->id.value();
- } else {
- strStream << "none";
- }
- strStream << " vs ";
- if (entryA->id) {
- strStream << "0x " << std::hex << entryA->id.value();
- } else {
- strStream << "none";
- }
- strStream << ")";
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- }
- diff |= emitResourceEntryDiff(context, apkA, pkgA, typeA, entryA.get(),
- apkB, pkgB, typeB, entryB);
+ strStream << "PRIVATE";
}
+ strStream << " vs ";
+ if (entryA->symbolStatus.state == SymbolState::kPublic) {
+ strStream << "PUBLIC";
+ } else {
+ strStream << "PRIVATE";
+ }
+ strStream << ")";
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ } else if (isIdDiff(entryA->symbolStatus, entryA->id,
+ entryB->symbolStatus, entryB->id)) {
+ std::stringstream strStream;
+ strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
+ << " has different public ID (";
+ if (entryB->id) {
+ strStream << "0x" << std::hex << entryB->id.value();
+ } else {
+ strStream << "none";
+ }
+ strStream << " vs ";
+ if (entryA->id) {
+ strStream << "0x " << std::hex << entryA->id.value();
+ } else {
+ strStream << "none";
+ }
+ strStream << ")";
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ }
+ diff |= emitResourceEntryDiff(context, apkA, pkgA, typeA, entryA.get(),
+ apkB, pkgB, typeB, entryB);
}
+ }
- // Check for any newly added entries.
- for (std::unique_ptr<ResourceEntry>& entryB : typeB->entries) {
- ResourceEntry* entryA = typeA->findEntry(entryB->name);
- if (!entryA) {
- std::stringstream strStream;
- strStream << "new entry " << pkgB->name << ":" << typeB->type << "/" << entryB->name;
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- }
+ // Check for any newly added entries.
+ for (std::unique_ptr<ResourceEntry>& entryB : typeB->entries) {
+ ResourceEntry* entryA = typeA->findEntry(entryB->name);
+ if (!entryA) {
+ std::stringstream strStream;
+ strStream << "new entry " << pkgB->name << ":" << typeB->type << "/"
+ << entryB->name;
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
}
- return diff;
+ }
+ return diff;
}
static bool emitResourcePackageDiff(IAaptContext* context, LoadedApk* apkA,
- ResourceTablePackage* pkgA,
- LoadedApk* apkB, ResourceTablePackage* pkgB) {
- bool diff = false;
- for (std::unique_ptr<ResourceTableType>& typeA : pkgA->types) {
- ResourceTableType* typeB = pkgB->findType(typeA->type);
- if (!typeB) {
- std::stringstream strStream;
- strStream << "missing " << pkgA->name << ":" << typeA->type;
- emitDiffLine(apkA->getSource(), strStream.str());
- diff = true;
+ ResourceTablePackage* pkgA, LoadedApk* apkB,
+ ResourceTablePackage* pkgB) {
+ bool diff = false;
+ for (std::unique_ptr<ResourceTableType>& typeA : pkgA->types) {
+ ResourceTableType* typeB = pkgB->findType(typeA->type);
+ if (!typeB) {
+ std::stringstream strStream;
+ strStream << "missing " << pkgA->name << ":" << typeA->type;
+ emitDiffLine(apkA->getSource(), strStream.str());
+ diff = true;
+ } else {
+ if (isSymbolVisibilityDifferent(typeA->symbolStatus,
+ typeB->symbolStatus)) {
+ std::stringstream strStream;
+ strStream << pkgA->name << ":" << typeA->type
+ << " has different visibility (";
+ if (typeB->symbolStatus.state == SymbolState::kPublic) {
+ strStream << "PUBLIC";
} else {
- if (isSymbolVisibilityDifferent(typeA->symbolStatus, typeB->symbolStatus)) {
- std::stringstream strStream;
- strStream << pkgA->name << ":" << typeA->type << " has different visibility (";
- if (typeB->symbolStatus.state == SymbolState::kPublic) {
- strStream << "PUBLIC";
- } else {
- strStream << "PRIVATE";
- }
- strStream << " vs ";
- if (typeA->symbolStatus.state == SymbolState::kPublic) {
- strStream << "PUBLIC";
- } else {
- strStream << "PRIVATE";
- }
- strStream << ")";
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- } else if (isIdDiff(typeA->symbolStatus, typeA->id, typeB->symbolStatus, typeB->id)) {
- std::stringstream strStream;
- strStream << pkgA->name << ":" << typeA->type << " has different public ID (";
- if (typeB->id) {
- strStream << "0x" << std::hex << typeB->id.value();
- } else {
- strStream << "none";
- }
- strStream << " vs ";
- if (typeA->id) {
- strStream << "0x " << std::hex << typeA->id.value();
- } else {
- strStream << "none";
- }
- strStream << ")";
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- }
- diff |= emitResourceTypeDiff(context, apkA, pkgA, typeA.get(), apkB, pkgB, typeB);
+ strStream << "PRIVATE";
}
+ strStream << " vs ";
+ if (typeA->symbolStatus.state == SymbolState::kPublic) {
+ strStream << "PUBLIC";
+ } else {
+ strStream << "PRIVATE";
+ }
+ strStream << ")";
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ } else if (isIdDiff(typeA->symbolStatus, typeA->id, typeB->symbolStatus,
+ typeB->id)) {
+ std::stringstream strStream;
+ strStream << pkgA->name << ":" << typeA->type
+ << " has different public ID (";
+ if (typeB->id) {
+ strStream << "0x" << std::hex << typeB->id.value();
+ } else {
+ strStream << "none";
+ }
+ strStream << " vs ";
+ if (typeA->id) {
+ strStream << "0x " << std::hex << typeA->id.value();
+ } else {
+ strStream << "none";
+ }
+ strStream << ")";
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ }
+ diff |= emitResourceTypeDiff(context, apkA, pkgA, typeA.get(), apkB, pkgB,
+ typeB);
}
+ }
- // Check for any newly added types.
- for (std::unique_ptr<ResourceTableType>& typeB : pkgB->types) {
- ResourceTableType* typeA = pkgA->findType(typeB->type);
- if (!typeA) {
- std::stringstream strStream;
- strStream << "new type " << pkgB->name << ":" << typeB->type;
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- }
+ // Check for any newly added types.
+ for (std::unique_ptr<ResourceTableType>& typeB : pkgB->types) {
+ ResourceTableType* typeA = pkgA->findType(typeB->type);
+ if (!typeA) {
+ std::stringstream strStream;
+ strStream << "new type " << pkgB->name << ":" << typeB->type;
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
}
- return diff;
+ }
+ return diff;
}
-static bool emitResourceTableDiff(IAaptContext* context, LoadedApk* apkA, LoadedApk* apkB) {
- ResourceTable* tableA = apkA->getResourceTable();
- ResourceTable* tableB = apkB->getResourceTable();
+static bool emitResourceTableDiff(IAaptContext* context, LoadedApk* apkA,
+ LoadedApk* apkB) {
+ ResourceTable* tableA = apkA->getResourceTable();
+ ResourceTable* tableB = apkB->getResourceTable();
- bool diff = false;
- for (std::unique_ptr<ResourceTablePackage>& pkgA : tableA->packages) {
- ResourceTablePackage* pkgB = tableB->findPackage(pkgA->name);
- if (!pkgB) {
- std::stringstream strStream;
- strStream << "missing package " << pkgA->name;
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
+ bool diff = false;
+ for (std::unique_ptr<ResourceTablePackage>& pkgA : tableA->packages) {
+ ResourceTablePackage* pkgB = tableB->findPackage(pkgA->name);
+ if (!pkgB) {
+ std::stringstream strStream;
+ strStream << "missing package " << pkgA->name;
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ } else {
+ if (pkgA->id != pkgB->id) {
+ std::stringstream strStream;
+ strStream << "package '" << pkgA->name << "' has different id (";
+ if (pkgB->id) {
+ strStream << "0x" << std::hex << pkgB->id.value();
} else {
- if (pkgA->id != pkgB->id) {
- std::stringstream strStream;
- strStream << "package '" << pkgA->name << "' has different id (";
- if (pkgB->id) {
- strStream << "0x" << std::hex << pkgB->id.value();
- } else {
- strStream << "none";
- }
- strStream << " vs ";
- if (pkgA->id) {
- strStream << "0x" << std::hex << pkgA->id.value();
- } else {
- strStream << "none";
- }
- strStream << ")";
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- }
- diff |= emitResourcePackageDiff(context, apkA, pkgA.get(), apkB, pkgB);
+ strStream << "none";
}
+ strStream << " vs ";
+ if (pkgA->id) {
+ strStream << "0x" << std::hex << pkgA->id.value();
+ } else {
+ strStream << "none";
+ }
+ strStream << ")";
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ }
+ diff |= emitResourcePackageDiff(context, apkA, pkgA.get(), apkB, pkgB);
}
+ }
- // Check for any newly added packages.
- for (std::unique_ptr<ResourceTablePackage>& pkgB : tableB->packages) {
- ResourceTablePackage* pkgA = tableA->findPackage(pkgB->name);
- if (!pkgA) {
- std::stringstream strStream;
- strStream << "new package " << pkgB->name;
- emitDiffLine(apkB->getSource(), strStream.str());
- diff = true;
- }
+ // Check for any newly added packages.
+ for (std::unique_ptr<ResourceTablePackage>& pkgB : tableB->packages) {
+ ResourceTablePackage* pkgA = tableA->findPackage(pkgB->name);
+ if (!pkgA) {
+ std::stringstream strStream;
+ strStream << "new package " << pkgB->name;
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
}
- return diff;
+ }
+ return diff;
}
class ZeroingReferenceVisitor : public ValueVisitor {
-public:
- using ValueVisitor::visit;
+ public:
+ using ValueVisitor::visit;
- void visit(Reference* ref) override {
- if (ref->name && ref->id) {
- if (ref->id.value().packageId() == 0x7f) {
- ref->id = {};
- }
- }
+ void visit(Reference* ref) override {
+ if (ref->name && ref->id) {
+ if (ref->id.value().packageId() == 0x7f) {
+ ref->id = {};
+ }
}
+ }
};
static void zeroOutAppReferences(ResourceTable* table) {
- ZeroingReferenceVisitor visitor;
- visitAllValuesInTable(table, &visitor);
+ ZeroingReferenceVisitor visitor;
+ visitAllValuesInTable(table, &visitor);
}
int diff(const std::vector<StringPiece>& args) {
- DiffContext context;
+ DiffContext context;
- Flags flags;
- if (!flags.parse("aapt2 diff", args, &std::cerr)) {
- return 1;
- }
+ Flags flags;
+ if (!flags.parse("aapt2 diff", args, &std::cerr)) {
+ return 1;
+ }
- if (flags.getArgs().size() != 2u) {
- std::cerr << "must have two apks as arguments.\n\n";
- flags.usage("aapt2 diff", &std::cerr);
- return 1;
- }
+ if (flags.getArgs().size() != 2u) {
+ std::cerr << "must have two apks as arguments.\n\n";
+ flags.usage("aapt2 diff", &std::cerr);
+ return 1;
+ }
- std::unique_ptr<LoadedApk> apkA = loadApkFromPath(&context, flags.getArgs()[0]);
- std::unique_ptr<LoadedApk> apkB = loadApkFromPath(&context, flags.getArgs()[1]);
- if (!apkA || !apkB) {
- return 1;
- }
+ std::unique_ptr<LoadedApk> apkA =
+ loadApkFromPath(&context, flags.getArgs()[0]);
+ std::unique_ptr<LoadedApk> apkB =
+ loadApkFromPath(&context, flags.getArgs()[1]);
+ if (!apkA || !apkB) {
+ return 1;
+ }
- // Zero out Application IDs in references.
- zeroOutAppReferences(apkA->getResourceTable());
- zeroOutAppReferences(apkB->getResourceTable());
+ // Zero out Application IDs in references.
+ zeroOutAppReferences(apkA->getResourceTable());
+ zeroOutAppReferences(apkB->getResourceTable());
- if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) {
- // We emitted a diff, so return 1 (failure).
- return 1;
- }
- return 0;
+ if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) {
+ // We emitted a diff, so return 1 (failure).
+ return 1;
+ }
+ return 0;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/dump/Dump.cpp b/tools/aapt2/dump/Dump.cpp
index f61ec94..3556cd88 100644
--- a/tools/aapt2/dump/Dump.cpp
+++ b/tools/aapt2/dump/Dump.cpp
@@ -28,183 +28,181 @@
namespace aapt {
-//struct DumpOptions {
+// struct DumpOptions {
//
//};
-void dumpCompiledFile(const pb::CompiledFile& pbFile, const void* data, size_t len,
- const Source& source, IAaptContext* context) {
- std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(pbFile, source,
- context->getDiagnostics());
- if (!file) {
- context->getDiagnostics()->warn(DiagMessage() << "failed to read compiled file");
- return;
- }
+void dumpCompiledFile(const pb::CompiledFile& pbFile, const void* data,
+ size_t len, const Source& source, IAaptContext* context) {
+ std::unique_ptr<ResourceFile> file =
+ deserializeCompiledFileFromPb(pbFile, source, context->getDiagnostics());
+ if (!file) {
+ context->getDiagnostics()->warn(DiagMessage()
+ << "failed to read compiled file");
+ return;
+ }
- std::cout << "Resource: " << file->name << "\n"
- << "Config: " << file->config << "\n"
- << "Source: " << file->source << "\n";
+ std::cout << "Resource: " << file->name << "\n"
+ << "Config: " << file->config << "\n"
+ << "Source: " << file->source << "\n";
}
void tryDumpFile(IAaptContext* context, const std::string& filePath) {
- std::unique_ptr<ResourceTable> table;
+ std::unique_ptr<ResourceTable> table;
- std::string err;
- std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::create(filePath, &err);
- if (zip) {
- io::IFile* file = zip->findFile("resources.arsc.flat");
- if (file) {
- std::unique_ptr<io::IData> data = file->openAsData();
- if (!data) {
- context->getDiagnostics()->error(DiagMessage(filePath)
- << "failed to open resources.arsc.flat");
- return;
- }
+ std::string err;
+ std::unique_ptr<io::ZipFileCollection> zip =
+ io::ZipFileCollection::create(filePath, &err);
+ if (zip) {
+ io::IFile* file = zip->findFile("resources.arsc.flat");
+ if (file) {
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ context->getDiagnostics()->error(
+ DiagMessage(filePath) << "failed to open resources.arsc.flat");
+ return;
+ }
- pb::ResourceTable pbTable;
- if (!pbTable.ParseFromArray(data->data(), data->size())) {
- context->getDiagnostics()->error(DiagMessage(filePath)
- << "invalid resources.arsc.flat");
- return;
- }
+ pb::ResourceTable pbTable;
+ if (!pbTable.ParseFromArray(data->data(), data->size())) {
+ context->getDiagnostics()->error(DiagMessage(filePath)
+ << "invalid resources.arsc.flat");
+ return;
+ }
- table = deserializeTableFromPb(
- pbTable, Source(filePath), context->getDiagnostics());
- if (!table) {
- return;
- }
- }
-
- if (!table) {
- file = zip->findFile("resources.arsc");
- if (file) {
- std::unique_ptr<io::IData> data = file->openAsData();
- if (!data) {
- context->getDiagnostics()->error(DiagMessage(filePath)
- << "failed to open resources.arsc");
- return;
- }
-
- table = util::make_unique<ResourceTable>();
- BinaryResourceParser parser(context, table.get(), Source(filePath),
- data->data(), data->size());
- if (!parser.parse()) {
- return;
- }
- }
- }
+ table = deserializeTableFromPb(pbTable, Source(filePath),
+ context->getDiagnostics());
+ if (!table) {
+ return;
+ }
}
if (!table) {
- Maybe<android::FileMap> file = file::mmapPath(filePath, &err);
- if (!file) {
- context->getDiagnostics()->error(DiagMessage(filePath) << err);
- return;
+ file = zip->findFile("resources.arsc");
+ if (file) {
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ context->getDiagnostics()->error(DiagMessage(filePath)
+ << "failed to open resources.arsc");
+ return;
}
- android::FileMap* fileMap = &file.value();
-
- // Try as a compiled table.
- pb::ResourceTable pbTable;
- if (pbTable.ParseFromArray(fileMap->getDataPtr(), fileMap->getDataLength())) {
- table = deserializeTableFromPb(pbTable, Source(filePath), context->getDiagnostics());
+ table = util::make_unique<ResourceTable>();
+ BinaryResourceParser parser(context, table.get(), Source(filePath),
+ data->data(), data->size());
+ if (!parser.parse()) {
+ return;
}
+ }
+ }
+ }
- if (!table) {
- // Try as a compiled file.
- CompiledFileInputStream input(fileMap->getDataPtr(), fileMap->getDataLength());
-
- uint32_t numFiles = 0;
- if (!input.ReadLittleEndian32(&numFiles)) {
- return;
- }
-
- for (uint32_t i = 0; i < numFiles; i++) {
- pb::CompiledFile compiledFile;
- if (!input.ReadCompiledFile(&compiledFile)) {
- context->getDiagnostics()->warn(DiagMessage() << "failed to read compiled file");
- return;
- }
-
- uint64_t offset, len;
- if (!input.ReadDataMetaData(&offset, &len)) {
- context->getDiagnostics()->warn(DiagMessage() << "failed to read meta data");
- return;
- }
-
- const void* data = static_cast<const uint8_t*>(fileMap->getDataPtr()) + offset;
- dumpCompiledFile(compiledFile, data, len, Source(filePath), context);
- }
- }
+ if (!table) {
+ Maybe<android::FileMap> file = file::mmapPath(filePath, &err);
+ if (!file) {
+ context->getDiagnostics()->error(DiagMessage(filePath) << err);
+ return;
}
- if (table) {
- DebugPrintTableOptions debugPrintTableOptions;
- debugPrintTableOptions.showSources = true;
- Debug::printTable(table.get(), debugPrintTableOptions);
+ android::FileMap* fileMap = &file.value();
+
+ // Try as a compiled table.
+ pb::ResourceTable pbTable;
+ if (pbTable.ParseFromArray(fileMap->getDataPtr(),
+ fileMap->getDataLength())) {
+ table = deserializeTableFromPb(pbTable, Source(filePath),
+ context->getDiagnostics());
}
+
+ if (!table) {
+ // Try as a compiled file.
+ CompiledFileInputStream input(fileMap->getDataPtr(),
+ fileMap->getDataLength());
+
+ uint32_t numFiles = 0;
+ if (!input.ReadLittleEndian32(&numFiles)) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < numFiles; i++) {
+ pb::CompiledFile compiledFile;
+ if (!input.ReadCompiledFile(&compiledFile)) {
+ context->getDiagnostics()->warn(DiagMessage()
+ << "failed to read compiled file");
+ return;
+ }
+
+ uint64_t offset, len;
+ if (!input.ReadDataMetaData(&offset, &len)) {
+ context->getDiagnostics()->warn(DiagMessage()
+ << "failed to read meta data");
+ return;
+ }
+
+ const void* data =
+ static_cast<const uint8_t*>(fileMap->getDataPtr()) + offset;
+ dumpCompiledFile(compiledFile, data, len, Source(filePath), context);
+ }
+ }
+ }
+
+ if (table) {
+ DebugPrintTableOptions debugPrintTableOptions;
+ debugPrintTableOptions.showSources = true;
+ Debug::printTable(table.get(), debugPrintTableOptions);
+ }
}
class DumpContext : public IAaptContext {
-public:
- IDiagnostics* getDiagnostics() override {
- return &mDiagnostics;
- }
+ public:
+ IDiagnostics* getDiagnostics() override { return &mDiagnostics; }
- NameMangler* getNameMangler() override {
- abort();
- return nullptr;
- }
+ NameMangler* getNameMangler() override {
+ abort();
+ return nullptr;
+ }
- const std::string& getCompilationPackage() override {
- static std::string empty;
- return empty;
- }
+ const std::string& getCompilationPackage() override {
+ static std::string empty;
+ return empty;
+ }
- uint8_t getPackageId() override {
- return 0;
- }
+ uint8_t getPackageId() override { return 0; }
- SymbolTable* getExternalSymbols() override {
- abort();
- return nullptr;
- }
+ SymbolTable* getExternalSymbols() override {
+ abort();
+ return nullptr;
+ }
- bool verbose() override {
- return mVerbose;
- }
+ bool verbose() override { return mVerbose; }
- void setVerbose(bool val) {
- mVerbose = val;
- }
+ void setVerbose(bool val) { mVerbose = val; }
- int getMinSdkVersion() override {
- return 0;
- }
+ int getMinSdkVersion() override { return 0; }
-private:
- StdErrDiagnostics mDiagnostics;
- bool mVerbose = false;
+ private:
+ StdErrDiagnostics mDiagnostics;
+ bool mVerbose = false;
};
/**
* Entry point for dump command.
*/
int dump(const std::vector<StringPiece>& args) {
- bool verbose = false;
- Flags flags = Flags()
- .optionalSwitch("-v", "increase verbosity of output", &verbose);
- if (!flags.parse("aapt2 dump", args, &std::cerr)) {
- return 1;
- }
+ bool verbose = false;
+ Flags flags =
+ Flags().optionalSwitch("-v", "increase verbosity of output", &verbose);
+ if (!flags.parse("aapt2 dump", args, &std::cerr)) {
+ return 1;
+ }
- DumpContext context;
- context.setVerbose(verbose);
+ DumpContext context;
+ context.setVerbose(verbose);
- for (const std::string& arg : flags.getArgs()) {
- tryDumpFile(&context, arg);
- }
- return 0;
+ for (const std::string& arg : flags.getArgs()) {
+ tryDumpFile(&context, arg);
+ }
+ return 0;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/filter/ConfigFilter.cpp b/tools/aapt2/filter/ConfigFilter.cpp
index 68a017d..5af996c 100644
--- a/tools/aapt2/filter/ConfigFilter.cpp
+++ b/tools/aapt2/filter/ConfigFilter.cpp
@@ -14,64 +14,68 @@
* limitations under the License.
*/
-#include "ConfigDescription.h"
#include "filter/ConfigFilter.h"
+#include "ConfigDescription.h"
#include <androidfw/ResourceTypes.h>
namespace aapt {
void AxisConfigFilter::addConfig(ConfigDescription config) {
- uint32_t diffMask = ConfigDescription::defaultConfig().diff(config);
+ uint32_t diffMask = ConfigDescription::defaultConfig().diff(config);
- // Ignore the version
- diffMask &= ~android::ResTable_config::CONFIG_VERSION;
+ // Ignore the version
+ diffMask &= ~android::ResTable_config::CONFIG_VERSION;
- // Ignore any densities. Those are best handled in --preferred-density
- if ((diffMask & android::ResTable_config::CONFIG_DENSITY) != 0) {
- config.density = 0;
- diffMask &= ~android::ResTable_config::CONFIG_DENSITY;
- }
+ // Ignore any densities. Those are best handled in --preferred-density
+ if ((diffMask & android::ResTable_config::CONFIG_DENSITY) != 0) {
+ config.density = 0;
+ diffMask &= ~android::ResTable_config::CONFIG_DENSITY;
+ }
- mConfigs.insert(std::make_pair(config, diffMask));
- mConfigMask |= diffMask;
+ mConfigs.insert(std::make_pair(config, diffMask));
+ mConfigMask |= diffMask;
}
bool AxisConfigFilter::match(const ConfigDescription& config) const {
- const uint32_t mask = ConfigDescription::defaultConfig().diff(config);
- if ((mConfigMask & mask) == 0) {
- // The two configurations don't have any common axis.
- return true;
- }
+ const uint32_t mask = ConfigDescription::defaultConfig().diff(config);
+ if ((mConfigMask & mask) == 0) {
+ // The two configurations don't have any common axis.
+ return true;
+ }
- uint32_t matchedAxis = 0;
- for (const auto& entry : mConfigs) {
- const ConfigDescription& target = entry.first;
- const uint32_t diffMask = entry.second;
- uint32_t diff = target.diff(config);
- if ((diff & diffMask) == 0) {
- // Mark the axis that was matched.
- matchedAxis |= diffMask;
- } else if ((diff & diffMask) == android::ResTable_config::CONFIG_LOCALE) {
- // If the locales differ, but the languages are the same and
- // the locale we are matching only has a language specified,
- // we match.
- if (config.language[0] &&
- memcmp(config.language, target.language, sizeof(config.language)) == 0) {
- if (config.country[0] == 0) {
- matchedAxis |= android::ResTable_config::CONFIG_LOCALE;
- }
- }
- } else if ((diff & diffMask) == android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) {
- // Special case if the smallest screen width doesn't match. We check that the
- // config being matched has a smaller screen width than the filter specified.
- if (config.smallestScreenWidthDp != 0 &&
- config.smallestScreenWidthDp < target.smallestScreenWidthDp) {
- matchedAxis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE;
- }
+ uint32_t matchedAxis = 0;
+ for (const auto& entry : mConfigs) {
+ const ConfigDescription& target = entry.first;
+ const uint32_t diffMask = entry.second;
+ uint32_t diff = target.diff(config);
+ if ((diff & diffMask) == 0) {
+ // Mark the axis that was matched.
+ matchedAxis |= diffMask;
+ } else if ((diff & diffMask) == android::ResTable_config::CONFIG_LOCALE) {
+ // If the locales differ, but the languages are the same and
+ // the locale we are matching only has a language specified,
+ // we match.
+ if (config.language[0] &&
+ memcmp(config.language, target.language, sizeof(config.language)) ==
+ 0) {
+ if (config.country[0] == 0) {
+ matchedAxis |= android::ResTable_config::CONFIG_LOCALE;
}
+ }
+ } else if ((diff & diffMask) ==
+ android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) {
+ // Special case if the smallest screen width doesn't match. We check that
+ // the
+ // config being matched has a smaller screen width than the filter
+ // specified.
+ if (config.smallestScreenWidthDp != 0 &&
+ config.smallestScreenWidthDp < target.smallestScreenWidthDp) {
+ matchedAxis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE;
+ }
}
- return matchedAxis == (mConfigMask & mask);
+ }
+ return matchedAxis == (mConfigMask & mask);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/filter/ConfigFilter.h b/tools/aapt2/filter/ConfigFilter.h
index 36e9c44..c50160b 100644
--- a/tools/aapt2/filter/ConfigFilter.h
+++ b/tools/aapt2/filter/ConfigFilter.h
@@ -28,34 +28,37 @@
* Matches ConfigDescriptions based on some pattern.
*/
class IConfigFilter {
-public:
- virtual ~IConfigFilter() = default;
+ public:
+ virtual ~IConfigFilter() = default;
- /**
- * Returns true if the filter matches the configuration, false otherwise.
- */
- virtual bool match(const ConfigDescription& config) const = 0;
+ /**
+ * Returns true if the filter matches the configuration, false otherwise.
+ */
+ virtual bool match(const ConfigDescription& config) const = 0;
};
/**
- * Implements config axis matching. An axis is one component of a configuration, like screen
- * density or locale. If an axis is specified in the filter, and the axis is specified in
- * the configuration to match, they must be compatible. Otherwise the configuration to match is
+ * Implements config axis matching. An axis is one component of a configuration,
+ * like screen
+ * density or locale. If an axis is specified in the filter, and the axis is
+ * specified in
+ * the configuration to match, they must be compatible. Otherwise the
+ * configuration to match is
* accepted.
*
* Used when handling "-c" options.
*/
class AxisConfigFilter : public IConfigFilter {
-public:
- void addConfig(ConfigDescription config);
+ public:
+ void addConfig(ConfigDescription config);
- bool match(const ConfigDescription& config) const override;
+ bool match(const ConfigDescription& config) const override;
-private:
- std::set<std::pair<ConfigDescription, uint32_t>> mConfigs;
- uint32_t mConfigMask = 0;
+ private:
+ std::set<std::pair<ConfigDescription, uint32_t>> mConfigs;
+ uint32_t mConfigMask = 0;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_FILTER_CONFIGFILTER_H */
diff --git a/tools/aapt2/filter/ConfigFilter_test.cpp b/tools/aapt2/filter/ConfigFilter_test.cpp
index f6b4955..edb40a8 100644
--- a/tools/aapt2/filter/ConfigFilter_test.cpp
+++ b/tools/aapt2/filter/ConfigFilter_test.cpp
@@ -22,91 +22,92 @@
namespace aapt {
TEST(ConfigFilterTest, EmptyFilterMatchesAnything) {
- AxisConfigFilter filter;
+ AxisConfigFilter filter;
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
}
TEST(ConfigFilterTest, MatchesConfigWithUnrelatedAxis) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("fr"));
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr"));
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
}
TEST(ConfigFilterTest, MatchesConfigWithSameValueAxis) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("fr"));
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr"));
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
}
TEST(ConfigFilterTest, MatchesConfigWithSameValueAxisAndOtherUnrelatedAxis) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("fr"));
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr"));
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-320dpi")));
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-320dpi")));
}
TEST(ConfigFilterTest, MatchesConfigWithOneMatchingAxis) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("fr-rFR"));
- filter.addConfig(test::parseConfigOrDie("sw360dp"));
- filter.addConfig(test::parseConfigOrDie("normal"));
- filter.addConfig(test::parseConfigOrDie("en-rUS"));
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr-rFR"));
+ filter.addConfig(test::parseConfigOrDie("sw360dp"));
+ filter.addConfig(test::parseConfigOrDie("normal"));
+ filter.addConfig(test::parseConfigOrDie("en-rUS"));
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("en")));
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("en")));
}
TEST(ConfigFilterTest, DoesNotMatchConfigWithDifferentValueAxis) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("fr"));
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr"));
- EXPECT_FALSE(filter.match(test::parseConfigOrDie("de")));
+ EXPECT_FALSE(filter.match(test::parseConfigOrDie("de")));
}
TEST(ConfigFilterTest, DoesNotMatchWhenOneQualifierIsExplicitlyNotMatched) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("fr-rFR"));
- filter.addConfig(test::parseConfigOrDie("en-rUS"));
- filter.addConfig(test::parseConfigOrDie("normal"));
- filter.addConfig(test::parseConfigOrDie("large"));
- filter.addConfig(test::parseConfigOrDie("xxhdpi"));
- filter.addConfig(test::parseConfigOrDie("sw320dp"));
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr-rFR"));
+ filter.addConfig(test::parseConfigOrDie("en-rUS"));
+ filter.addConfig(test::parseConfigOrDie("normal"));
+ filter.addConfig(test::parseConfigOrDie("large"));
+ filter.addConfig(test::parseConfigOrDie("xxhdpi"));
+ filter.addConfig(test::parseConfigOrDie("sw320dp"));
- EXPECT_FALSE(filter.match(test::parseConfigOrDie("fr-sw600dp-v13")));
+ EXPECT_FALSE(filter.match(test::parseConfigOrDie("fr-sw600dp-v13")));
}
TEST(ConfigFilterTest, MatchesSmallestWidthWhenSmaller) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("sw600dp"));
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("sw600dp"));
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-sw320dp-v13")));
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-sw320dp-v13")));
}
TEST(ConfigFilterTest, MatchesConfigWithSameLanguageButNoRegionSpecified) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("de-rDE"));
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("de-rDE"));
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("de")));
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("de")));
}
TEST(ConfigFilterTest, IgnoresVersion) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("normal-v4"));
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("normal-v4"));
- // The configs don't match on any axis besides version, which should be ignored.
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("sw600dp-v13")));
+ // The configs don't match on any axis besides version, which should be
+ // ignored.
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("sw600dp-v13")));
}
TEST(ConfigFilterTest, MatchesConfigWithRegion) {
- AxisConfigFilter filter;
- filter.addConfig(test::parseConfigOrDie("kok"));
- filter.addConfig(test::parseConfigOrDie("kok-rIN"));
- filter.addConfig(test::parseConfigOrDie("kok-v419"));
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("kok"));
+ filter.addConfig(test::parseConfigOrDie("kok-rIN"));
+ filter.addConfig(test::parseConfigOrDie("kok-v419"));
- EXPECT_TRUE(filter.match(test::parseConfigOrDie("kok-rIN")));
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("kok-rIN")));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp
index 3a244c0..ae08f65 100644
--- a/tools/aapt2/flatten/Archive.cpp
+++ b/tools/aapt2/flatten/Archive.cpp
@@ -18,167 +18,169 @@
#include "util/Files.h"
#include "util/StringPiece.h"
+#include <ziparchive/zip_writer.h>
#include <cstdio>
#include <memory>
#include <string>
#include <vector>
-#include <ziparchive/zip_writer.h>
namespace aapt {
namespace {
struct DirectoryWriter : public IArchiveWriter {
- std::string mOutDir;
- std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
+ std::string mOutDir;
+ std::unique_ptr<FILE, decltype(fclose)*> mFile = {nullptr, fclose};
- bool open(IDiagnostics* diag, const StringPiece& outDir) {
- mOutDir = outDir.toString();
- file::FileType type = file::getFileType(mOutDir);
- if (type == file::FileType::kNonexistant) {
- diag->error(DiagMessage() << "directory " << mOutDir << " does not exist");
- return false;
- } else if (type != file::FileType::kDirectory) {
- diag->error(DiagMessage() << mOutDir << " is not a directory");
- return false;
- }
- return true;
+ bool open(IDiagnostics* diag, const StringPiece& outDir) {
+ mOutDir = outDir.toString();
+ file::FileType type = file::getFileType(mOutDir);
+ if (type == file::FileType::kNonexistant) {
+ diag->error(DiagMessage() << "directory " << mOutDir
+ << " does not exist");
+ return false;
+ } else if (type != file::FileType::kDirectory) {
+ diag->error(DiagMessage() << mOutDir << " is not a directory");
+ return false;
+ }
+ return true;
+ }
+
+ bool startEntry(const StringPiece& path, uint32_t flags) override {
+ if (mFile) {
+ return false;
}
- bool startEntry(const StringPiece& path, uint32_t flags) override {
- if (mFile) {
- return false;
- }
+ std::string fullPath = mOutDir;
+ file::appendPath(&fullPath, path);
+ file::mkdirs(file::getStem(fullPath));
- std::string fullPath = mOutDir;
- file::appendPath(&fullPath, path);
- file::mkdirs(file::getStem(fullPath));
+ mFile = {fopen(fullPath.data(), "wb"), fclose};
+ if (!mFile) {
+ return false;
+ }
+ return true;
+ }
- mFile = { fopen(fullPath.data(), "wb"), fclose };
- if (!mFile) {
- return false;
- }
- return true;
+ bool writeEntry(const BigBuffer& buffer) override {
+ if (!mFile) {
+ return false;
}
- bool writeEntry(const BigBuffer& buffer) override {
- if (!mFile) {
- return false;
- }
-
- for (const BigBuffer::Block& b : buffer) {
- if (fwrite(b.buffer.get(), 1, b.size, mFile.get()) != b.size) {
- mFile.reset(nullptr);
- return false;
- }
- }
- return true;
- }
-
- bool writeEntry(const void* data, size_t len) override {
- if (fwrite(data, 1, len, mFile.get()) != len) {
- mFile.reset(nullptr);
- return false;
- }
- return true;
- }
-
- bool finishEntry() override {
- if (!mFile) {
- return false;
- }
+ for (const BigBuffer::Block& b : buffer) {
+ if (fwrite(b.buffer.get(), 1, b.size, mFile.get()) != b.size) {
mFile.reset(nullptr);
- return true;
+ return false;
+ }
}
+ return true;
+ }
+
+ bool writeEntry(const void* data, size_t len) override {
+ if (fwrite(data, 1, len, mFile.get()) != len) {
+ mFile.reset(nullptr);
+ return false;
+ }
+ return true;
+ }
+
+ bool finishEntry() override {
+ if (!mFile) {
+ return false;
+ }
+ mFile.reset(nullptr);
+ return true;
+ }
};
struct ZipFileWriter : public IArchiveWriter {
- std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
- std::unique_ptr<ZipWriter> mWriter;
+ std::unique_ptr<FILE, decltype(fclose)*> mFile = {nullptr, fclose};
+ std::unique_ptr<ZipWriter> mWriter;
- bool open(IDiagnostics* diag, const StringPiece& path) {
- mFile = { fopen(path.data(), "w+b"), fclose };
- if (!mFile) {
- diag->error(DiagMessage() << "failed to open " << path << ": " << strerror(errno));
- return false;
- }
- mWriter = util::make_unique<ZipWriter>(mFile.get());
- return true;
+ bool open(IDiagnostics* diag, const StringPiece& path) {
+ mFile = {fopen(path.data(), "w+b"), fclose};
+ if (!mFile) {
+ diag->error(DiagMessage() << "failed to open " << path << ": "
+ << strerror(errno));
+ return false;
+ }
+ mWriter = util::make_unique<ZipWriter>(mFile.get());
+ return true;
+ }
+
+ bool startEntry(const StringPiece& path, uint32_t flags) override {
+ if (!mWriter) {
+ return false;
}
- bool startEntry(const StringPiece& path, uint32_t flags) override {
- if (!mWriter) {
- return false;
- }
-
- size_t zipFlags = 0;
- if (flags & ArchiveEntry::kCompress) {
- zipFlags |= ZipWriter::kCompress;
- }
-
- if (flags & ArchiveEntry::kAlign) {
- zipFlags |= ZipWriter::kAlign32;
- }
-
- int32_t result = mWriter->StartEntry(path.data(), zipFlags);
- if (result != 0) {
- return false;
- }
- return true;
+ size_t zipFlags = 0;
+ if (flags & ArchiveEntry::kCompress) {
+ zipFlags |= ZipWriter::kCompress;
}
- bool writeEntry(const void* data, size_t len) override {
- int32_t result = mWriter->WriteBytes(data, len);
- if (result != 0) {
- return false;
- }
- return true;
+ if (flags & ArchiveEntry::kAlign) {
+ zipFlags |= ZipWriter::kAlign32;
}
- bool writeEntry(const BigBuffer& buffer) override {
- for (const BigBuffer::Block& b : buffer) {
- int32_t result = mWriter->WriteBytes(b.buffer.get(), b.size);
- if (result != 0) {
- return false;
- }
- }
- return true;
+ int32_t result = mWriter->StartEntry(path.data(), zipFlags);
+ if (result != 0) {
+ return false;
}
+ return true;
+ }
- bool finishEntry() override {
- int32_t result = mWriter->FinishEntry();
- if (result != 0) {
- return false;
- }
- return true;
+ bool writeEntry(const void* data, size_t len) override {
+ int32_t result = mWriter->WriteBytes(data, len);
+ if (result != 0) {
+ return false;
}
+ return true;
+ }
- virtual ~ZipFileWriter() {
- if (mWriter) {
- mWriter->Finish();
- }
+ bool writeEntry(const BigBuffer& buffer) override {
+ for (const BigBuffer::Block& b : buffer) {
+ int32_t result = mWriter->WriteBytes(b.buffer.get(), b.size);
+ if (result != 0) {
+ return false;
+ }
}
+ return true;
+ }
+
+ bool finishEntry() override {
+ int32_t result = mWriter->FinishEntry();
+ if (result != 0) {
+ return false;
+ }
+ return true;
+ }
+
+ virtual ~ZipFileWriter() {
+ if (mWriter) {
+ mWriter->Finish();
+ }
+ }
};
-} // namespace
+} // namespace
-std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
- const StringPiece& path) {
-
- std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
- if (!writer->open(diag, path)) {
- return {};
- }
- return std::move(writer);
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(
+ IDiagnostics* diag, const StringPiece& path) {
+ std::unique_ptr<DirectoryWriter> writer =
+ util::make_unique<DirectoryWriter>();
+ if (!writer->open(diag, path)) {
+ return {};
+ }
+ return std::move(writer);
}
-std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag,
- const StringPiece& path) {
- std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
- if (!writer->open(diag, path)) {
- return {};
- }
- return std::move(writer);
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(
+ IDiagnostics* diag, const StringPiece& path) {
+ std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
+ if (!writer->open(diag, path)) {
+ return {};
+ }
+ return std::move(writer);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
index 96d8512..46cd0ac 100644
--- a/tools/aapt2/flatten/Archive.h
+++ b/tools/aapt2/flatten/Archive.h
@@ -31,37 +31,37 @@
namespace aapt {
struct ArchiveEntry {
- enum : uint32_t {
- kCompress = 0x01,
- kAlign = 0x02,
- };
+ enum : uint32_t {
+ kCompress = 0x01,
+ kAlign = 0x02,
+ };
- std::string path;
- uint32_t flags;
- size_t uncompressedSize;
+ std::string path;
+ uint32_t flags;
+ size_t uncompressedSize;
};
class IArchiveWriter : public google::protobuf::io::CopyingOutputStream {
-public:
- virtual ~IArchiveWriter() = default;
+ public:
+ virtual ~IArchiveWriter() = default;
- virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0;
- virtual bool writeEntry(const BigBuffer& buffer) = 0;
- virtual bool writeEntry(const void* data, size_t len) = 0;
- virtual bool finishEntry() = 0;
+ virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0;
+ virtual bool writeEntry(const BigBuffer& buffer) = 0;
+ virtual bool writeEntry(const void* data, size_t len) = 0;
+ virtual bool finishEntry() = 0;
- // CopyingOutputStream implementations.
- bool Write(const void* buffer, int size) override {
- return writeEntry(buffer, size);
- }
+ // CopyingOutputStream implementations.
+ bool Write(const void* buffer, int size) override {
+ return writeEntry(buffer, size);
+ }
};
-std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
- const StringPiece& path);
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(
+ IDiagnostics* diag, const StringPiece& path);
-std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag,
- const StringPiece& path);
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(
+ IDiagnostics* diag, const StringPiece& path);
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_FLATTEN_ARCHIVE_H */
diff --git a/tools/aapt2/flatten/ChunkWriter.h b/tools/aapt2/flatten/ChunkWriter.h
index de1d87a..852afd4 100644
--- a/tools/aapt2/flatten/ChunkWriter.h
+++ b/tools/aapt2/flatten/ChunkWriter.h
@@ -25,63 +25,56 @@
namespace aapt {
class ChunkWriter {
-private:
- BigBuffer* mBuffer;
- size_t mStartSize = 0;
- android::ResChunk_header* mHeader = nullptr;
+ private:
+ BigBuffer* mBuffer;
+ size_t mStartSize = 0;
+ android::ResChunk_header* mHeader = nullptr;
-public:
- explicit inline ChunkWriter(BigBuffer* buffer) : mBuffer(buffer) {
- }
+ public:
+ explicit inline ChunkWriter(BigBuffer* buffer) : mBuffer(buffer) {}
- ChunkWriter(const ChunkWriter&) = delete;
- ChunkWriter& operator=(const ChunkWriter&) = delete;
- ChunkWriter(ChunkWriter&&) = default;
- ChunkWriter& operator=(ChunkWriter&&) = default;
+ ChunkWriter(const ChunkWriter&) = delete;
+ ChunkWriter& operator=(const ChunkWriter&) = delete;
+ ChunkWriter(ChunkWriter&&) = default;
+ ChunkWriter& operator=(ChunkWriter&&) = default;
- template <typename T>
- inline T* startChunk(uint16_t type) {
- mStartSize = mBuffer->size();
- T* chunk = mBuffer->nextBlock<T>();
- mHeader = &chunk->header;
- mHeader->type = util::hostToDevice16(type);
- mHeader->headerSize = util::hostToDevice16(sizeof(T));
- return chunk;
- }
+ template <typename T>
+ inline T* startChunk(uint16_t type) {
+ mStartSize = mBuffer->size();
+ T* chunk = mBuffer->nextBlock<T>();
+ mHeader = &chunk->header;
+ mHeader->type = util::hostToDevice16(type);
+ mHeader->headerSize = util::hostToDevice16(sizeof(T));
+ return chunk;
+ }
- template <typename T>
- inline T* nextBlock(size_t count = 1) {
- return mBuffer->nextBlock<T>(count);
- }
+ template <typename T>
+ inline T* nextBlock(size_t count = 1) {
+ return mBuffer->nextBlock<T>(count);
+ }
- inline BigBuffer* getBuffer() {
- return mBuffer;
- }
+ inline BigBuffer* getBuffer() { return mBuffer; }
- inline android::ResChunk_header* getChunkHeader() {
- return mHeader;
- }
+ inline android::ResChunk_header* getChunkHeader() { return mHeader; }
- inline size_t size() {
- return mBuffer->size() - mStartSize;
- }
+ inline size_t size() { return mBuffer->size() - mStartSize; }
- inline android::ResChunk_header* finish() {
- mBuffer->align4();
- mHeader->size = util::hostToDevice32(mBuffer->size() - mStartSize);
- return mHeader;
- }
+ inline android::ResChunk_header* finish() {
+ mBuffer->align4();
+ mHeader->size = util::hostToDevice32(mBuffer->size() - mStartSize);
+ return mHeader;
+ }
};
template <>
inline android::ResChunk_header* ChunkWriter::startChunk(uint16_t type) {
- mStartSize = mBuffer->size();
- mHeader = mBuffer->nextBlock<android::ResChunk_header>();
- mHeader->type = util::hostToDevice16(type);
- mHeader->headerSize = util::hostToDevice16(sizeof(android::ResChunk_header));
- return mHeader;
+ mStartSize = mBuffer->size();
+ mHeader = mBuffer->nextBlock<android::ResChunk_header>();
+ mHeader->type = util::hostToDevice16(type);
+ mHeader->headerSize = util::hostToDevice16(sizeof(android::ResChunk_header));
+ return mHeader;
}
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_FLATTEN_CHUNKWRITER_H */
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
index 3e20ad6..0b19240 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -22,15 +22,16 @@
namespace aapt {
/**
- * An alternative struct to use instead of ResTable_map_entry. This one is a standard_layout
+ * An alternative struct to use instead of ResTable_map_entry. This one is a
+ * standard_layout
* struct.
*/
struct ResTable_entry_ext {
- android::ResTable_entry entry;
- android::ResTable_ref parent;
- uint32_t count;
+ android::ResTable_entry entry;
+ android::ResTable_ref parent;
+ uint32_t count;
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H
+#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index d5067b1..d4ea6c0 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -25,9 +25,9 @@
#include <android-base/macros.h>
#include <algorithm>
+#include <numeric>
#include <sstream>
#include <type_traits>
-#include <numeric>
using namespace android;
@@ -37,438 +37,454 @@
template <typename T>
static bool cmpIds(const T* a, const T* b) {
- return a->id.value() < b->id.value();
+ return a->id.value() < b->id.value();
}
static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) {
- if (len == 0) {
- return;
- }
+ if (len == 0) {
+ return;
+ }
- size_t i;
- const char16_t* srcData = src.data();
- for (i = 0; i < len - 1 && i < src.size(); i++) {
- dst[i] = util::hostToDevice16((uint16_t) srcData[i]);
- }
- dst[i] = 0;
+ size_t i;
+ const char16_t* srcData = src.data();
+ for (i = 0; i < len - 1 && i < src.size(); i++) {
+ dst[i] = util::hostToDevice16((uint16_t)srcData[i]);
+ }
+ dst[i] = 0;
}
static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) {
- if (a.key.id) {
- if (b.key.id) {
- return a.key.id.value() < b.key.id.value();
- }
- return true;
- } else if (!b.key.id) {
- return a.key.name.value() < b.key.name.value();
- }
- return false;
+ if (a.key.id) {
+ if (b.key.id) {
+ return a.key.id.value() < b.key.id.value();
+ }
+ return true;
+ } else if (!b.key.id) {
+ return a.key.name.value() < b.key.name.value();
+ }
+ return false;
}
struct FlatEntry {
- ResourceEntry* entry;
- Value* value;
+ ResourceEntry* entry;
+ Value* value;
- // The entry string pool index to the entry's name.
- uint32_t entryKey;
+ // The entry string pool index to the entry's name.
+ uint32_t entryKey;
};
class MapFlattenVisitor : public RawValueVisitor {
-public:
- using RawValueVisitor::visit;
+ public:
+ using RawValueVisitor::visit;
- MapFlattenVisitor(ResTable_entry_ext* outEntry, BigBuffer* buffer) :
- mOutEntry(outEntry), mBuffer(buffer) {
+ MapFlattenVisitor(ResTable_entry_ext* outEntry, BigBuffer* buffer)
+ : mOutEntry(outEntry), mBuffer(buffer) {}
+
+ void visit(Attribute* attr) override {
+ {
+ Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE));
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask);
+ flattenEntry(&key, &val);
}
- void visit(Attribute* attr) override {
- {
- Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE));
- BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask);
- flattenEntry(&key, &val);
- }
-
- if (attr->minInt != std::numeric_limits<int32_t>::min()) {
- Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN));
- BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->minInt));
- flattenEntry(&key, &val);
- }
-
- if (attr->maxInt != std::numeric_limits<int32_t>::max()) {
- Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX));
- BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->maxInt));
- flattenEntry(&key, &val);
- }
-
- for (Attribute::Symbol& s : attr->symbols) {
- BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
- flattenEntry(&s.symbol, &val);
- }
+ if (attr->minInt != std::numeric_limits<int32_t>::min()) {
+ Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN));
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC,
+ static_cast<uint32_t>(attr->minInt));
+ flattenEntry(&key, &val);
}
- void visit(Style* style) override {
- if (style->parent) {
- const Reference& parentRef = style->parent.value();
- assert(parentRef.id && "parent has no ID");
- mOutEntry->parent.ident = util::hostToDevice32(parentRef.id.value().id);
- }
-
- // Sort the style.
- std::sort(style->entries.begin(), style->entries.end(), cmpStyleEntries);
-
- for (Style::Entry& entry : style->entries) {
- flattenEntry(&entry.key, entry.value.get());
- }
+ if (attr->maxInt != std::numeric_limits<int32_t>::max()) {
+ Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX));
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC,
+ static_cast<uint32_t>(attr->maxInt));
+ flattenEntry(&key, &val);
}
- void visit(Styleable* styleable) override {
- for (auto& attrRef : styleable->entries) {
- BinaryPrimitive val(Res_value{});
- flattenEntry(&attrRef, &val);
- }
+ for (Attribute::Symbol& s : attr->symbols) {
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
+ flattenEntry(&s.symbol, &val);
+ }
+ }
+ void visit(Style* style) override {
+ if (style->parent) {
+ const Reference& parentRef = style->parent.value();
+ assert(parentRef.id && "parent has no ID");
+ mOutEntry->parent.ident = util::hostToDevice32(parentRef.id.value().id);
}
- void visit(Array* array) override {
- for (auto& item : array->items) {
- ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
- flattenValue(item.get(), outEntry);
- outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
- mEntryCount++;
- }
+ // Sort the style.
+ std::sort(style->entries.begin(), style->entries.end(), cmpStyleEntries);
+
+ for (Style::Entry& entry : style->entries) {
+ flattenEntry(&entry.key, entry.value.get());
}
+ }
- void visit(Plural* plural) override {
- const size_t count = plural->values.size();
- for (size_t i = 0; i < count; i++) {
- if (!plural->values[i]) {
- continue;
- }
-
- ResourceId q;
- switch (i) {
- case Plural::Zero:
- q.id = android::ResTable_map::ATTR_ZERO;
- break;
-
- case Plural::One:
- q.id = android::ResTable_map::ATTR_ONE;
- break;
-
- case Plural::Two:
- q.id = android::ResTable_map::ATTR_TWO;
- break;
-
- case Plural::Few:
- q.id = android::ResTable_map::ATTR_FEW;
- break;
-
- case Plural::Many:
- q.id = android::ResTable_map::ATTR_MANY;
- break;
-
- case Plural::Other:
- q.id = android::ResTable_map::ATTR_OTHER;
- break;
-
- default:
- assert(false);
- break;
- }
-
- Reference key(q);
- flattenEntry(&key, plural->values[i].get());
- }
+ void visit(Styleable* styleable) override {
+ for (auto& attrRef : styleable->entries) {
+ BinaryPrimitive val(Res_value{});
+ flattenEntry(&attrRef, &val);
}
+ }
- /**
- * Call this after visiting a Value. This will finish any work that
- * needs to be done to prepare the entry.
- */
- void finish() {
- mOutEntry->count = util::hostToDevice32(mEntryCount);
+ void visit(Array* array) override {
+ for (auto& item : array->items) {
+ ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
+ flattenValue(item.get(), outEntry);
+ outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
+ mEntryCount++;
}
+ }
-private:
- void flattenKey(Reference* key, ResTable_map* outEntry) {
- assert(key->id && "key has no ID");
- outEntry->name.ident = util::hostToDevice32(key->id.value().id);
+ void visit(Plural* plural) override {
+ const size_t count = plural->values.size();
+ for (size_t i = 0; i < count; i++) {
+ if (!plural->values[i]) {
+ continue;
+ }
+
+ ResourceId q;
+ switch (i) {
+ case Plural::Zero:
+ q.id = android::ResTable_map::ATTR_ZERO;
+ break;
+
+ case Plural::One:
+ q.id = android::ResTable_map::ATTR_ONE;
+ break;
+
+ case Plural::Two:
+ q.id = android::ResTable_map::ATTR_TWO;
+ break;
+
+ case Plural::Few:
+ q.id = android::ResTable_map::ATTR_FEW;
+ break;
+
+ case Plural::Many:
+ q.id = android::ResTable_map::ATTR_MANY;
+ break;
+
+ case Plural::Other:
+ q.id = android::ResTable_map::ATTR_OTHER;
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+
+ Reference key(q);
+ flattenEntry(&key, plural->values[i].get());
}
+ }
- void flattenValue(Item* value, ResTable_map* outEntry) {
- bool result = value->flatten(&outEntry->value);
- assert(result && "flatten failed");
- }
+ /**
+ * Call this after visiting a Value. This will finish any work that
+ * needs to be done to prepare the entry.
+ */
+ void finish() { mOutEntry->count = util::hostToDevice32(mEntryCount); }
- void flattenEntry(Reference* key, Item* value) {
- ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
- flattenKey(key, outEntry);
- flattenValue(value, outEntry);
- outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
- mEntryCount++;
- }
+ private:
+ void flattenKey(Reference* key, ResTable_map* outEntry) {
+ assert(key->id && "key has no ID");
+ outEntry->name.ident = util::hostToDevice32(key->id.value().id);
+ }
- ResTable_entry_ext* mOutEntry;
- BigBuffer* mBuffer;
- size_t mEntryCount = 0;
+ void flattenValue(Item* value, ResTable_map* outEntry) {
+ bool result = value->flatten(&outEntry->value);
+ assert(result && "flatten failed");
+ }
+
+ void flattenEntry(Reference* key, Item* value) {
+ ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
+ flattenKey(key, outEntry);
+ flattenValue(value, outEntry);
+ outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
+ mEntryCount++;
+ }
+
+ ResTable_entry_ext* mOutEntry;
+ BigBuffer* mBuffer;
+ size_t mEntryCount = 0;
};
class PackageFlattener {
-public:
- PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package) :
- mDiag(diag), mPackage(package) {
+ public:
+ PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package)
+ : mDiag(diag), mPackage(package) {}
+
+ bool flattenPackage(BigBuffer* buffer) {
+ ChunkWriter pkgWriter(buffer);
+ ResTable_package* pkgHeader =
+ pkgWriter.startChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE);
+ pkgHeader->id = util::hostToDevice32(mPackage->id.value());
+
+ if (mPackage->name.size() >= arraysize(pkgHeader->name)) {
+ mDiag->error(DiagMessage() << "package name '" << mPackage->name
+ << "' is too long");
+ return false;
}
- bool flattenPackage(BigBuffer* buffer) {
- ChunkWriter pkgWriter(buffer);
- ResTable_package* pkgHeader = pkgWriter.startChunk<ResTable_package>(
- RES_TABLE_PACKAGE_TYPE);
- pkgHeader->id = util::hostToDevice32(mPackage->id.value());
+ // Copy the package name in device endianness.
+ strcpy16_htod(pkgHeader->name, arraysize(pkgHeader->name),
+ util::utf8ToUtf16(mPackage->name));
- if (mPackage->name.size() >= arraysize(pkgHeader->name)) {
- mDiag->error(DiagMessage() <<
- "package name '" << mPackage->name << "' is too long");
- return false;
- }
+ // Serialize the types. We do this now so that our type and key strings
+ // are populated. We write those first.
+ BigBuffer typeBuffer(1024);
+ flattenTypes(&typeBuffer);
- // Copy the package name in device endianness.
- strcpy16_htod(pkgHeader->name, arraysize(pkgHeader->name),
- util::utf8ToUtf16(mPackage->name));
+ pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size());
+ StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool);
- // Serialize the types. We do this now so that our type and key strings
- // are populated. We write those first.
- BigBuffer typeBuffer(1024);
- flattenTypes(&typeBuffer);
+ pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
+ StringPool::flattenUtf8(pkgWriter.getBuffer(), mKeyPool);
- pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size());
- StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool);
+ // Append the types.
+ buffer->appendBuffer(std::move(typeBuffer));
- pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
- StringPool::flattenUtf8(pkgWriter.getBuffer(), mKeyPool);
+ pkgWriter.finish();
+ return true;
+ }
- // Append the types.
- buffer->appendBuffer(std::move(typeBuffer));
+ private:
+ IDiagnostics* mDiag;
+ ResourceTablePackage* mPackage;
+ StringPool mTypePool;
+ StringPool mKeyPool;
- pkgWriter.finish();
- return true;
- }
-
-private:
- IDiagnostics* mDiag;
- ResourceTablePackage* mPackage;
- StringPool mTypePool;
- StringPool mKeyPool;
-
- template <typename T, bool IsItem>
- T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
- static_assert(std::is_same<ResTable_entry, T>::value ||
+ template <typename T, bool IsItem>
+ T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
+ static_assert(std::is_same<ResTable_entry, T>::value ||
std::is_same<ResTable_entry_ext, T>::value,
- "T must be ResTable_entry or ResTable_entry_ext");
+ "T must be ResTable_entry or ResTable_entry_ext");
- T* result = buffer->nextBlock<T>();
- ResTable_entry* outEntry = (ResTable_entry*)(result);
- if (entry->entry->symbolStatus.state == SymbolState::kPublic) {
- outEntry->flags |= ResTable_entry::FLAG_PUBLIC;
- }
-
- if (entry->value->isWeak()) {
- outEntry->flags |= ResTable_entry::FLAG_WEAK;
- }
-
- if (!IsItem) {
- outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
- }
-
- outEntry->flags = util::hostToDevice16(outEntry->flags);
- outEntry->key.index = util::hostToDevice32(entry->entryKey);
- outEntry->size = util::hostToDevice16(sizeof(T));
- return result;
+ T* result = buffer->nextBlock<T>();
+ ResTable_entry* outEntry = (ResTable_entry*)(result);
+ if (entry->entry->symbolStatus.state == SymbolState::kPublic) {
+ outEntry->flags |= ResTable_entry::FLAG_PUBLIC;
}
- bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
- if (Item* item = valueCast<Item>(entry->value)) {
- writeEntry<ResTable_entry, true>(entry, buffer);
- Res_value* outValue = buffer->nextBlock<Res_value>();
- bool result = item->flatten(outValue);
- assert(result && "flatten failed");
- outValue->size = util::hostToDevice16(sizeof(*outValue));
- } else {
- ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
- MapFlattenVisitor visitor(outEntry, buffer);
- entry->value->accept(&visitor);
- visitor.finish();
- }
- return true;
+ if (entry->value->isWeak()) {
+ outEntry->flags |= ResTable_entry::FLAG_WEAK;
}
- bool flattenConfig(const ResourceTableType* type, const ConfigDescription& config,
- std::vector<FlatEntry>* entries, BigBuffer* buffer) {
- ChunkWriter typeWriter(buffer);
- ResTable_type* typeHeader = typeWriter.startChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
- typeHeader->id = type->id.value();
- typeHeader->config = config;
- typeHeader->config.swapHtoD();
-
- auto maxAccum = [](uint32_t max, const std::unique_ptr<ResourceEntry>& a) -> uint32_t {
- return std::max(max, (uint32_t) a->id.value());
- };
-
- // Find the largest entry ID. That is how many entries we will have.
- const uint32_t entryCount =
- std::accumulate(type->entries.begin(), type->entries.end(), 0, maxAccum) + 1;
-
- typeHeader->entryCount = util::hostToDevice32(entryCount);
- uint32_t* indices = typeWriter.nextBlock<uint32_t>(entryCount);
-
- assert((size_t) entryCount <= std::numeric_limits<uint16_t>::max() + 1);
- memset(indices, 0xff, entryCount * sizeof(uint32_t));
-
- typeHeader->entriesStart = util::hostToDevice32(typeWriter.size());
-
- const size_t entryStart = typeWriter.getBuffer()->size();
- for (FlatEntry& flatEntry : *entries) {
- assert(flatEntry.entry->id.value() < entryCount);
- indices[flatEntry.entry->id.value()] = util::hostToDevice32(
- typeWriter.getBuffer()->size() - entryStart);
- if (!flattenValue(&flatEntry, typeWriter.getBuffer())) {
- mDiag->error(DiagMessage()
- << "failed to flatten resource '"
- << ResourceNameRef(mPackage->name, type->type, flatEntry.entry->name)
- << "' for configuration '" << config << "'");
- return false;
- }
- }
- typeWriter.finish();
- return true;
+ if (!IsItem) {
+ outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
}
- std::vector<ResourceTableType*> collectAndSortTypes() {
- std::vector<ResourceTableType*> sortedTypes;
- for (auto& type : mPackage->types) {
- if (type->type == ResourceType::kStyleable) {
- // Styleables aren't real Resource Types, they are represented in the R.java
- // file.
- continue;
- }
+ outEntry->flags = util::hostToDevice16(outEntry->flags);
+ outEntry->key.index = util::hostToDevice32(entry->entryKey);
+ outEntry->size = util::hostToDevice16(sizeof(T));
+ return result;
+ }
- assert(type->id && "type must have an ID set");
+ bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
+ if (Item* item = valueCast<Item>(entry->value)) {
+ writeEntry<ResTable_entry, true>(entry, buffer);
+ Res_value* outValue = buffer->nextBlock<Res_value>();
+ bool result = item->flatten(outValue);
+ assert(result && "flatten failed");
+ outValue->size = util::hostToDevice16(sizeof(*outValue));
+ } else {
+ ResTable_entry_ext* outEntry =
+ writeEntry<ResTable_entry_ext, false>(entry, buffer);
+ MapFlattenVisitor visitor(outEntry, buffer);
+ entry->value->accept(&visitor);
+ visitor.finish();
+ }
+ return true;
+ }
- sortedTypes.push_back(type.get());
- }
- std::sort(sortedTypes.begin(), sortedTypes.end(), cmpIds<ResourceTableType>);
- return sortedTypes;
+ bool flattenConfig(const ResourceTableType* type,
+ const ConfigDescription& config,
+ std::vector<FlatEntry>* entries, BigBuffer* buffer) {
+ ChunkWriter typeWriter(buffer);
+ ResTable_type* typeHeader =
+ typeWriter.startChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
+ typeHeader->id = type->id.value();
+ typeHeader->config = config;
+ typeHeader->config.swapHtoD();
+
+ auto maxAccum = [](uint32_t max,
+ const std::unique_ptr<ResourceEntry>& a) -> uint32_t {
+ return std::max(max, (uint32_t)a->id.value());
+ };
+
+ // Find the largest entry ID. That is how many entries we will have.
+ const uint32_t entryCount =
+ std::accumulate(type->entries.begin(), type->entries.end(), 0,
+ maxAccum) +
+ 1;
+
+ typeHeader->entryCount = util::hostToDevice32(entryCount);
+ uint32_t* indices = typeWriter.nextBlock<uint32_t>(entryCount);
+
+ assert((size_t)entryCount <= std::numeric_limits<uint16_t>::max() + 1);
+ memset(indices, 0xff, entryCount * sizeof(uint32_t));
+
+ typeHeader->entriesStart = util::hostToDevice32(typeWriter.size());
+
+ const size_t entryStart = typeWriter.getBuffer()->size();
+ for (FlatEntry& flatEntry : *entries) {
+ assert(flatEntry.entry->id.value() < entryCount);
+ indices[flatEntry.entry->id.value()] =
+ util::hostToDevice32(typeWriter.getBuffer()->size() - entryStart);
+ if (!flattenValue(&flatEntry, typeWriter.getBuffer())) {
+ mDiag->error(DiagMessage()
+ << "failed to flatten resource '"
+ << ResourceNameRef(mPackage->name, type->type,
+ flatEntry.entry->name)
+ << "' for configuration '" << config << "'");
+ return false;
+ }
+ }
+ typeWriter.finish();
+ return true;
+ }
+
+ std::vector<ResourceTableType*> collectAndSortTypes() {
+ std::vector<ResourceTableType*> sortedTypes;
+ for (auto& type : mPackage->types) {
+ if (type->type == ResourceType::kStyleable) {
+ // Styleables aren't real Resource Types, they are represented in the
+ // R.java
+ // file.
+ continue;
+ }
+
+ assert(type->id && "type must have an ID set");
+
+ sortedTypes.push_back(type.get());
+ }
+ std::sort(sortedTypes.begin(), sortedTypes.end(),
+ cmpIds<ResourceTableType>);
+ return sortedTypes;
+ }
+
+ std::vector<ResourceEntry*> collectAndSortEntries(ResourceTableType* type) {
+ // Sort the entries by entry ID.
+ std::vector<ResourceEntry*> sortedEntries;
+ for (auto& entry : type->entries) {
+ assert(entry->id && "entry must have an ID set");
+ sortedEntries.push_back(entry.get());
+ }
+ std::sort(sortedEntries.begin(), sortedEntries.end(),
+ cmpIds<ResourceEntry>);
+ return sortedEntries;
+ }
+
+ bool flattenTypeSpec(ResourceTableType* type,
+ std::vector<ResourceEntry*>* sortedEntries,
+ BigBuffer* buffer) {
+ ChunkWriter typeSpecWriter(buffer);
+ ResTable_typeSpec* specHeader =
+ typeSpecWriter.startChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE);
+ specHeader->id = type->id.value();
+
+ if (sortedEntries->empty()) {
+ typeSpecWriter.finish();
+ return true;
}
- std::vector<ResourceEntry*> collectAndSortEntries(ResourceTableType* type) {
- // Sort the entries by entry ID.
- std::vector<ResourceEntry*> sortedEntries;
- for (auto& entry : type->entries) {
- assert(entry->id && "entry must have an ID set");
- sortedEntries.push_back(entry.get());
+ // We can't just take the size of the vector. There may be holes in the
+ // entry ID space.
+ // Since the entries are sorted by ID, the last one will be the biggest.
+ const size_t numEntries = sortedEntries->back()->id.value() + 1;
+
+ specHeader->entryCount = util::hostToDevice32(numEntries);
+
+ // Reserve space for the masks of each resource in this type. These
+ // show for which configuration axis the resource changes.
+ uint32_t* configMasks = typeSpecWriter.nextBlock<uint32_t>(numEntries);
+
+ const size_t actualNumEntries = sortedEntries->size();
+ for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) {
+ ResourceEntry* entry = sortedEntries->at(entryIndex);
+
+ // Populate the config masks for this entry.
+
+ if (entry->symbolStatus.state == SymbolState::kPublic) {
+ configMasks[entry->id.value()] |=
+ util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
+ }
+
+ const size_t configCount = entry->values.size();
+ for (size_t i = 0; i < configCount; i++) {
+ const ConfigDescription& config = entry->values[i]->config;
+ for (size_t j = i + 1; j < configCount; j++) {
+ configMasks[entry->id.value()] |=
+ util::hostToDevice32(config.diff(entry->values[j]->config));
}
- std::sort(sortedEntries.begin(), sortedEntries.end(), cmpIds<ResourceEntry>);
- return sortedEntries;
+ }
}
+ typeSpecWriter.finish();
+ return true;
+ }
- bool flattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries,
- BigBuffer* buffer) {
- ChunkWriter typeSpecWriter(buffer);
- ResTable_typeSpec* specHeader = typeSpecWriter.startChunk<ResTable_typeSpec>(
- RES_TABLE_TYPE_SPEC_TYPE);
- specHeader->id = type->id.value();
+ bool flattenTypes(BigBuffer* buffer) {
+ // Sort the types by their IDs. They will be inserted into the StringPool in
+ // this order.
+ std::vector<ResourceTableType*> sortedTypes = collectAndSortTypes();
- if (sortedEntries->empty()) {
- typeSpecWriter.finish();
- return true;
+ size_t expectedTypeId = 1;
+ for (ResourceTableType* type : sortedTypes) {
+ // If there is a gap in the type IDs, fill in the StringPool
+ // with empty values until we reach the ID we expect.
+ while (type->id.value() > expectedTypeId) {
+ std::stringstream typeName;
+ typeName << "?" << expectedTypeId;
+ mTypePool.makeRef(typeName.str());
+ expectedTypeId++;
+ }
+ expectedTypeId++;
+ mTypePool.makeRef(toString(type->type));
+
+ std::vector<ResourceEntry*> sortedEntries = collectAndSortEntries(type);
+
+ if (!flattenTypeSpec(type, &sortedEntries, buffer)) {
+ return false;
+ }
+
+ // The binary resource table lists resource entries for each
+ // configuration.
+ // We store them inverted, where a resource entry lists the values for
+ // each
+ // configuration available. Here we reverse this to match the binary
+ // table.
+ std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap;
+ for (ResourceEntry* entry : sortedEntries) {
+ const uint32_t keyIndex =
+ (uint32_t)mKeyPool.makeRef(entry->name).getIndex();
+
+ // Group values by configuration.
+ for (auto& configValue : entry->values) {
+ configToEntryListMap[configValue->config].push_back(
+ FlatEntry{entry, configValue->value.get(), keyIndex});
}
+ }
- // We can't just take the size of the vector. There may be holes in the entry ID space.
- // Since the entries are sorted by ID, the last one will be the biggest.
- const size_t numEntries = sortedEntries->back()->id.value() + 1;
-
- specHeader->entryCount = util::hostToDevice32(numEntries);
-
- // Reserve space for the masks of each resource in this type. These
- // show for which configuration axis the resource changes.
- uint32_t* configMasks = typeSpecWriter.nextBlock<uint32_t>(numEntries);
-
- const size_t actualNumEntries = sortedEntries->size();
- for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) {
- ResourceEntry* entry = sortedEntries->at(entryIndex);
-
- // Populate the config masks for this entry.
-
- if (entry->symbolStatus.state == SymbolState::kPublic) {
- configMasks[entry->id.value()] |=
- util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
- }
-
- const size_t configCount = entry->values.size();
- for (size_t i = 0; i < configCount; i++) {
- const ConfigDescription& config = entry->values[i]->config;
- for (size_t j = i + 1; j < configCount; j++) {
- configMasks[entry->id.value()] |= util::hostToDevice32(
- config.diff(entry->values[j]->config));
- }
- }
+ // Flatten a configuration value.
+ for (auto& entry : configToEntryListMap) {
+ if (!flattenConfig(type, entry.first, &entry.second, buffer)) {
+ return false;
}
- typeSpecWriter.finish();
- return true;
+ }
}
-
- bool flattenTypes(BigBuffer* buffer) {
- // Sort the types by their IDs. They will be inserted into the StringPool in this order.
- std::vector<ResourceTableType*> sortedTypes = collectAndSortTypes();
-
- size_t expectedTypeId = 1;
- for (ResourceTableType* type : sortedTypes) {
- // If there is a gap in the type IDs, fill in the StringPool
- // with empty values until we reach the ID we expect.
- while (type->id.value() > expectedTypeId) {
- std::stringstream typeName;
- typeName << "?" << expectedTypeId;
- mTypePool.makeRef(typeName.str());
- expectedTypeId++;
- }
- expectedTypeId++;
- mTypePool.makeRef(toString(type->type));
-
- std::vector<ResourceEntry*> sortedEntries = collectAndSortEntries(type);
-
- if (!flattenTypeSpec(type, &sortedEntries, buffer)) {
- return false;
- }
-
- // The binary resource table lists resource entries for each configuration.
- // We store them inverted, where a resource entry lists the values for each
- // configuration available. Here we reverse this to match the binary table.
- std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap;
- for (ResourceEntry* entry : sortedEntries) {
- const uint32_t keyIndex = (uint32_t) mKeyPool.makeRef(entry->name).getIndex();
-
- // Group values by configuration.
- for (auto& configValue : entry->values) {
- configToEntryListMap[configValue->config].push_back(FlatEntry{
- entry, configValue->value.get(), keyIndex });
- }
- }
-
- // Flatten a configuration value.
- for (auto& entry : configToEntryListMap) {
- if (!flattenConfig(type, entry.first, &entry.second, buffer)) {
- return false;
- }
- }
- }
- return true;
- }
+ return true;
+ }
};
-} // namespace
+} // namespace
bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) {
- // We must do this before writing the resources, since the string pool IDs may change.
- table->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+ // We must do this before writing the resources, since the string pool IDs may
+ // change.
+ table->stringPool.sort(
+ [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
int diff = a.context.priority - b.context.priority;
if (diff < 0) return true;
if (diff > 0) return false;
@@ -476,31 +492,32 @@
if (diff < 0) return true;
if (diff > 0) return false;
return a.value < b.value;
- });
- table->stringPool.prune();
+ });
+ table->stringPool.prune();
- // Write the ResTable header.
- ChunkWriter tableWriter(mBuffer);
- ResTable_header* tableHeader = tableWriter.startChunk<ResTable_header>(RES_TABLE_TYPE);
- tableHeader->packageCount = util::hostToDevice32(table->packages.size());
+ // Write the ResTable header.
+ ChunkWriter tableWriter(mBuffer);
+ ResTable_header* tableHeader =
+ tableWriter.startChunk<ResTable_header>(RES_TABLE_TYPE);
+ tableHeader->packageCount = util::hostToDevice32(table->packages.size());
- // Flatten the values string pool.
- StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool);
+ // Flatten the values string pool.
+ StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool);
- BigBuffer packageBuffer(1024);
+ BigBuffer packageBuffer(1024);
- // Flatten each package.
- for (auto& package : table->packages) {
- PackageFlattener flattener(context->getDiagnostics(), package.get());
- if (!flattener.flattenPackage(&packageBuffer)) {
- return false;
- }
+ // Flatten each package.
+ for (auto& package : table->packages) {
+ PackageFlattener flattener(context->getDiagnostics(), package.get());
+ if (!flattener.flattenPackage(&packageBuffer)) {
+ return false;
}
+ }
- // Finally merge all the packages into the main buffer.
- tableWriter.getBuffer()->appendBuffer(std::move(packageBuffer));
- tableWriter.finish();
- return true;
+ // Finally merge all the packages into the main buffer.
+ tableWriter.getBuffer()->appendBuffer(std::move(packageBuffer));
+ tableWriter.finish();
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/flatten/TableFlattener.h b/tools/aapt2/flatten/TableFlattener.h
index b416f20..91f9654 100644
--- a/tools/aapt2/flatten/TableFlattener.h
+++ b/tools/aapt2/flatten/TableFlattener.h
@@ -25,16 +25,15 @@
class ResourceTable;
class TableFlattener : public IResourceTableConsumer {
-public:
- explicit TableFlattener(BigBuffer* buffer) : mBuffer(buffer) {
- }
+ public:
+ explicit TableFlattener(BigBuffer* buffer) : mBuffer(buffer) {}
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ bool consume(IAaptContext* context, ResourceTable* table) override;
-private:
- BigBuffer* mBuffer;
+ private:
+ BigBuffer* mBuffer;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_FLATTEN_TABLEFLATTENER_H */
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
index b25bfa7..a7706bd 100644
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#include "ResourceUtils.h"
#include "flatten/TableFlattener.h"
+#include "ResourceUtils.h"
#include "test/Test.h"
#include "unflatten/BinaryResourceParser.h"
#include "util/Util.h"
@@ -25,195 +25,207 @@
namespace aapt {
class TableFlattenerTest : public ::testing::Test {
-public:
- void SetUp() override {
- mContext = test::ContextBuilder()
- .setCompilationPackage("com.app.test")
- .setPackageId(0x7f)
- .build();
+ public:
+ void SetUp() override {
+ mContext = test::ContextBuilder()
+ .setCompilationPackage("com.app.test")
+ .setPackageId(0x7f)
+ .build();
+ }
+
+ ::testing::AssertionResult flatten(ResourceTable* table, ResTable* outTable) {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(&buffer);
+ if (!flattener.consume(mContext.get(), table)) {
+ return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
}
- ::testing::AssertionResult flatten(ResourceTable* table, ResTable* outTable) {
- BigBuffer buffer(1024);
- TableFlattener flattener(&buffer);
- if (!flattener.consume(mContext.get(), table)) {
- return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
- }
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ if (outTable->add(data.get(), buffer.size(), -1, true) != NO_ERROR) {
+ return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
+ }
+ return ::testing::AssertionSuccess();
+ }
- std::unique_ptr<uint8_t[]> data = util::copy(buffer);
- if (outTable->add(data.get(), buffer.size(), -1, true) != NO_ERROR) {
- return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
- }
- return ::testing::AssertionSuccess();
+ ::testing::AssertionResult flatten(ResourceTable* table,
+ ResourceTable* outTable) {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(&buffer);
+ if (!flattener.consume(mContext.get(), table)) {
+ return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
}
- ::testing::AssertionResult flatten(ResourceTable* table, ResourceTable* outTable) {
- BigBuffer buffer(1024);
- TableFlattener flattener(&buffer);
- if (!flattener.consume(mContext.get(), table)) {
- return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
- }
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ BinaryResourceParser parser(mContext.get(), outTable, {}, data.get(),
+ buffer.size());
+ if (!parser.parse()) {
+ return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
+ }
+ return ::testing::AssertionSuccess();
+ }
- std::unique_ptr<uint8_t[]> data = util::copy(buffer);
- BinaryResourceParser parser(mContext.get(), outTable, {}, data.get(), buffer.size());
- if (!parser.parse()) {
- return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
- }
- return ::testing::AssertionSuccess();
+ ::testing::AssertionResult exists(ResTable* table,
+ const StringPiece& expectedName,
+ const ResourceId& expectedId,
+ const ConfigDescription& expectedConfig,
+ const uint8_t expectedDataType,
+ const uint32_t expectedData,
+ const uint32_t expectedSpecFlags) {
+ const ResourceName expectedResName = test::parseNameOrDie(expectedName);
+
+ table->setParameters(&expectedConfig);
+
+ ResTable_config config;
+ Res_value val;
+ uint32_t specFlags;
+ if (table->getResource(expectedId.id, &val, false, 0, &specFlags, &config) <
+ 0) {
+ return ::testing::AssertionFailure() << "could not find resource with";
}
- ::testing::AssertionResult exists(ResTable* table,
- const StringPiece& expectedName,
- const ResourceId& expectedId,
- const ConfigDescription& expectedConfig,
- const uint8_t expectedDataType, const uint32_t expectedData,
- const uint32_t expectedSpecFlags) {
- const ResourceName expectedResName = test::parseNameOrDie(expectedName);
-
- table->setParameters(&expectedConfig);
-
- ResTable_config config;
- Res_value val;
- uint32_t specFlags;
- if (table->getResource(expectedId.id, &val, false, 0, &specFlags, &config) < 0) {
- return ::testing::AssertionFailure() << "could not find resource with";
- }
-
- if (expectedDataType != val.dataType) {
- return ::testing::AssertionFailure()
- << "expected data type "
- << std::hex << (int) expectedDataType << " but got data type "
- << (int) val.dataType << std::dec << " instead";
- }
-
- if (expectedData != val.data) {
- return ::testing::AssertionFailure()
- << "expected data "
- << std::hex << expectedData << " but got data "
- << val.data << std::dec << " instead";
- }
-
- if (expectedSpecFlags != specFlags) {
- return ::testing::AssertionFailure()
- << "expected specFlags "
- << std::hex << expectedSpecFlags << " but got specFlags "
- << specFlags << std::dec << " instead";
- }
-
- ResTable::resource_name actualName;
- if (!table->getResourceName(expectedId.id, false, &actualName)) {
- return ::testing::AssertionFailure() << "failed to find resource name";
- }
-
- Maybe<ResourceName> resName = ResourceUtils::toResourceName(actualName);
- if (!resName) {
- return ::testing::AssertionFailure()
- << "expected name '" << expectedResName << "' but got '"
- << StringPiece16(actualName.package, actualName.packageLen)
- << ":"
- << StringPiece16(actualName.type, actualName.typeLen)
- << "/"
- << StringPiece16(actualName.name, actualName.nameLen)
- << "'";
- }
-
- if (expectedConfig != config) {
- return ::testing::AssertionFailure()
- << "expected config '" << expectedConfig << "' but got '"
- << ConfigDescription(config) << "'";
- }
- return ::testing::AssertionSuccess();
+ if (expectedDataType != val.dataType) {
+ return ::testing::AssertionFailure()
+ << "expected data type " << std::hex << (int)expectedDataType
+ << " but got data type " << (int)val.dataType << std::dec
+ << " instead";
}
-private:
- std::unique_ptr<IAaptContext> mContext;
+ if (expectedData != val.data) {
+ return ::testing::AssertionFailure()
+ << "expected data " << std::hex << expectedData << " but got data "
+ << val.data << std::dec << " instead";
+ }
+
+ if (expectedSpecFlags != specFlags) {
+ return ::testing::AssertionFailure()
+ << "expected specFlags " << std::hex << expectedSpecFlags
+ << " but got specFlags " << specFlags << std::dec << " instead";
+ }
+
+ ResTable::resource_name actualName;
+ if (!table->getResourceName(expectedId.id, false, &actualName)) {
+ return ::testing::AssertionFailure() << "failed to find resource name";
+ }
+
+ Maybe<ResourceName> resName = ResourceUtils::toResourceName(actualName);
+ if (!resName) {
+ return ::testing::AssertionFailure()
+ << "expected name '" << expectedResName << "' but got '"
+ << StringPiece16(actualName.package, actualName.packageLen) << ":"
+ << StringPiece16(actualName.type, actualName.typeLen) << "/"
+ << StringPiece16(actualName.name, actualName.nameLen) << "'";
+ }
+
+ if (expectedConfig != config) {
+ return ::testing::AssertionFailure() << "expected config '"
+ << expectedConfig << "' but got '"
+ << ConfigDescription(config) << "'";
+ }
+ return ::testing::AssertionSuccess();
+ }
+
+ private:
+ std::unique_ptr<IAaptContext> mContext;
};
TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("com.app.test", 0x7f)
- .addSimple("com.app.test:id/one", ResourceId(0x7f020000))
- .addSimple("com.app.test:id/two", ResourceId(0x7f020001))
- .addValue("com.app.test:id/three", ResourceId(0x7f020002),
- test::buildReference("com.app.test:id/one", ResourceId(0x7f020000)))
- .addValue("com.app.test:integer/one", ResourceId(0x7f030000),
- util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
- .addValue("com.app.test:integer/one", test::parseConfigOrDie("v1"),
- ResourceId(0x7f030000),
- util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
- .addString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
- .addString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .setPackageId("com.app.test", 0x7f)
+ .addSimple("com.app.test:id/one", ResourceId(0x7f020000))
+ .addSimple("com.app.test:id/two", ResourceId(0x7f020001))
+ .addValue("com.app.test:id/three", ResourceId(0x7f020002),
+ test::buildReference("com.app.test:id/one",
+ ResourceId(0x7f020000)))
+ .addValue("com.app.test:integer/one", ResourceId(0x7f030000),
+ util::make_unique<BinaryPrimitive>(
+ uint8_t(Res_value::TYPE_INT_DEC), 1u))
+ .addValue("com.app.test:integer/one", test::parseConfigOrDie("v1"),
+ ResourceId(0x7f030000),
+ util::make_unique<BinaryPrimitive>(
+ uint8_t(Res_value::TYPE_INT_DEC), 2u))
+ .addString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
+ .addString("com.app.test:layout/bar", ResourceId(0x7f050000),
+ "res/layout/bar.xml")
+ .build();
- ResTable resTable;
- ASSERT_TRUE(flatten(table.get(), &resTable));
+ ResTable resTable;
+ ASSERT_TRUE(flatten(table.get(), &resTable));
- EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020000), {},
- Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+ EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020000),
+ {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
- EXPECT_TRUE(exists(&resTable, "com.app.test:id/two", ResourceId(0x7f020001), {},
- Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+ EXPECT_TRUE(exists(&resTable, "com.app.test:id/two", ResourceId(0x7f020001),
+ {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
- EXPECT_TRUE(exists(&resTable, "com.app.test:id/three", ResourceId(0x7f020002), {},
- Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
+ EXPECT_TRUE(exists(&resTable, "com.app.test:id/three", ResourceId(0x7f020002),
+ {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
- EXPECT_TRUE(exists(&resTable, "com.app.test:integer/one", ResourceId(0x7f030000),
- {}, Res_value::TYPE_INT_DEC, 1u,
- ResTable_config::CONFIG_VERSION));
+ EXPECT_TRUE(exists(&resTable, "com.app.test:integer/one",
+ ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
+ ResTable_config::CONFIG_VERSION));
- EXPECT_TRUE(exists(&resTable, "com.app.test:integer/one", ResourceId(0x7f030000),
- test::parseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u,
- ResTable_config::CONFIG_VERSION));
+ EXPECT_TRUE(exists(&resTable, "com.app.test:integer/one",
+ ResourceId(0x7f030000), test::parseConfigOrDie("v1"),
+ Res_value::TYPE_INT_DEC, 2u,
+ ResTable_config::CONFIG_VERSION));
- std::u16string fooStr = u"foo";
- ssize_t idx = resTable.getTableStringBlock(0)->indexOfString(fooStr.data(), fooStr.size());
- ASSERT_GE(idx, 0);
- EXPECT_TRUE(exists(&resTable, "com.app.test:string/test", ResourceId(0x7f040000),
- {}, Res_value::TYPE_STRING, (uint32_t) idx, 0u));
+ std::u16string fooStr = u"foo";
+ ssize_t idx = resTable.getTableStringBlock(0)->indexOfString(fooStr.data(),
+ fooStr.size());
+ ASSERT_GE(idx, 0);
+ EXPECT_TRUE(exists(&resTable, "com.app.test:string/test",
+ ResourceId(0x7f040000), {}, Res_value::TYPE_STRING,
+ (uint32_t)idx, 0u));
- std::u16string barPath = u"res/layout/bar.xml";
- idx = resTable.getTableStringBlock(0)->indexOfString(barPath.data(), barPath.size());
- ASSERT_GE(idx, 0);
- EXPECT_TRUE(exists(&resTable, "com.app.test:layout/bar", ResourceId(0x7f050000), {},
- Res_value::TYPE_STRING, (uint32_t) idx, 0u));
+ std::u16string barPath = u"res/layout/bar.xml";
+ idx = resTable.getTableStringBlock(0)->indexOfString(barPath.data(),
+ barPath.size());
+ ASSERT_GE(idx, 0);
+ EXPECT_TRUE(exists(&resTable, "com.app.test:layout/bar",
+ ResourceId(0x7f050000), {}, Res_value::TYPE_STRING,
+ (uint32_t)idx, 0u));
}
TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("com.app.test", 0x7f)
- .addSimple("com.app.test:id/one", ResourceId(0x7f020001))
- .addSimple("com.app.test:id/three", ResourceId(0x7f020003))
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .setPackageId("com.app.test", 0x7f)
+ .addSimple("com.app.test:id/one", ResourceId(0x7f020001))
+ .addSimple("com.app.test:id/three", ResourceId(0x7f020003))
+ .build();
- ResTable resTable;
- ASSERT_TRUE(flatten(table.get(), &resTable));
+ ResTable resTable;
+ ASSERT_TRUE(flatten(table.get(), &resTable));
- EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020001), {},
- Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
- EXPECT_TRUE(exists(&resTable, "com.app.test:id/three", ResourceId(0x7f020003), {},
- Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+ EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020001),
+ {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+ EXPECT_TRUE(exists(&resTable, "com.app.test:id/three", ResourceId(0x7f020003),
+ {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
}
TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
- Attribute attr(false);
- attr.typeMask = android::ResTable_map::TYPE_INTEGER;
- attr.minInt = 10;
- attr.maxInt = 23;
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("android", 0x01)
- .addValue("android:attr/foo", ResourceId(0x01010000),
- util::make_unique<Attribute>(attr))
- .build();
+ Attribute attr(false);
+ attr.typeMask = android::ResTable_map::TYPE_INTEGER;
+ attr.minInt = 10;
+ attr.maxInt = 23;
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .setPackageId("android", 0x01)
+ .addValue("android:attr/foo", ResourceId(0x01010000),
+ util::make_unique<Attribute>(attr))
+ .build();
- ResourceTable result;
- ASSERT_TRUE(flatten(table.get(), &result));
+ ResourceTable result;
+ ASSERT_TRUE(flatten(table.get(), &result));
- Attribute* actualAttr = test::getValue<Attribute>(&result, "android:attr/foo");
- ASSERT_NE(nullptr, actualAttr);
- EXPECT_EQ(attr.isWeak(), actualAttr->isWeak());
- EXPECT_EQ(attr.typeMask, actualAttr->typeMask);
- EXPECT_EQ(attr.minInt, actualAttr->minInt);
- EXPECT_EQ(attr.maxInt, actualAttr->maxInt);
+ Attribute* actualAttr =
+ test::getValue<Attribute>(&result, "android:attr/foo");
+ ASSERT_NE(nullptr, actualAttr);
+ EXPECT_EQ(attr.isWeak(), actualAttr->isWeak());
+ EXPECT_EQ(attr.typeMask, actualAttr->typeMask);
+ EXPECT_EQ(attr.minInt, actualAttr->minInt);
+ EXPECT_EQ(attr.maxInt, actualAttr->maxInt);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp
index ed5b60f..b1536d5 100644
--- a/tools/aapt2/flatten/XmlFlattener.cpp
+++ b/tools/aapt2/flatten/XmlFlattener.cpp
@@ -14,16 +14,16 @@
* limitations under the License.
*/
+#include "flatten/XmlFlattener.h"
#include "SdkConstants.h"
#include "flatten/ChunkWriter.h"
#include "flatten/ResourceTypeExtensions.h"
-#include "flatten/XmlFlattener.h"
#include "xml/XmlDom.h"
#include <androidfw/ResourceTypes.h>
+#include <utils/misc.h>
#include <algorithm>
#include <map>
-#include <utils/misc.h>
#include <vector>
using namespace android;
@@ -35,300 +35,316 @@
constexpr uint32_t kLowPriority = 0xffffffffu;
struct XmlFlattenerVisitor : public xml::Visitor {
- using xml::Visitor::visit;
+ using xml::Visitor::visit;
- BigBuffer* mBuffer;
- XmlFlattenerOptions mOptions;
- StringPool mPool;
- std::map<uint8_t, StringPool> mPackagePools;
+ BigBuffer* mBuffer;
+ XmlFlattenerOptions mOptions;
+ StringPool mPool;
+ std::map<uint8_t, StringPool> mPackagePools;
- struct StringFlattenDest {
- StringPool::Ref ref;
- ResStringPool_ref* dest;
- };
- std::vector<StringFlattenDest> mStringRefs;
+ struct StringFlattenDest {
+ StringPool::Ref ref;
+ ResStringPool_ref* dest;
+ };
+ std::vector<StringFlattenDest> mStringRefs;
- // Scratch vector to filter attributes. We avoid allocations
- // making this a member.
- std::vector<xml::Attribute*> mFilteredAttrs;
+ // Scratch vector to filter attributes. We avoid allocations
+ // making this a member.
+ std::vector<xml::Attribute*> mFilteredAttrs;
+ XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options)
+ : mBuffer(buffer), mOptions(options) {}
- XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) :
- mBuffer(buffer), mOptions(options) {
+ void addString(const StringPiece& str, uint32_t priority,
+ android::ResStringPool_ref* dest,
+ bool treatEmptyStringAsNull = false) {
+ if (str.empty() && treatEmptyStringAsNull) {
+ // Some parts of the runtime treat null differently than empty string.
+ dest->index = util::deviceToHost32(-1);
+ } else {
+ mStringRefs.push_back(StringFlattenDest{
+ mPool.makeRef(str, StringPool::Context(priority)), dest});
+ }
+ }
+
+ void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
+ mStringRefs.push_back(StringFlattenDest{ref, dest});
+ }
+
+ void writeNamespace(xml::Namespace* node, uint16_t type) {
+ ChunkWriter writer(mBuffer);
+
+ ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type);
+ flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
+ flatNode->comment.index = util::hostToDevice32(-1);
+
+ ResXMLTree_namespaceExt* flatNs =
+ writer.nextBlock<ResXMLTree_namespaceExt>();
+ addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
+ addString(node->namespaceUri, kLowPriority, &flatNs->uri);
+
+ writer.finish();
+ }
+
+ void visit(xml::Namespace* node) override {
+ if (node->namespaceUri == xml::kSchemaTools) {
+ // Skip dedicated tools namespace.
+ xml::Visitor::visit(node);
+ } else {
+ writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
+ xml::Visitor::visit(node);
+ writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
+ }
+ }
+
+ void visit(xml::Text* node) override {
+ if (util::trimWhitespace(node->text).empty()) {
+ // Skip whitespace only text nodes.
+ return;
}
- void addString(const StringPiece& str, uint32_t priority, android::ResStringPool_ref* dest,
- bool treatEmptyStringAsNull = false) {
- if (str.empty() && treatEmptyStringAsNull) {
- // Some parts of the runtime treat null differently than empty string.
- dest->index = util::deviceToHost32(-1);
- } else {
- mStringRefs.push_back(StringFlattenDest{
- mPool.makeRef(str, StringPool::Context{ priority }),
- dest });
- }
+ ChunkWriter writer(mBuffer);
+ ResXMLTree_node* flatNode =
+ writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
+ flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
+ flatNode->comment.index = util::hostToDevice32(-1);
+
+ ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>();
+ addString(node->text, kLowPriority, &flatText->data);
+
+ writer.finish();
+ }
+
+ void visit(xml::Element* node) override {
+ {
+ ChunkWriter startWriter(mBuffer);
+ ResXMLTree_node* flatNode =
+ startWriter.startChunk<ResXMLTree_node>(RES_XML_START_ELEMENT_TYPE);
+ flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
+ flatNode->comment.index = util::hostToDevice32(-1);
+
+ ResXMLTree_attrExt* flatElem =
+ startWriter.nextBlock<ResXMLTree_attrExt>();
+
+ // A missing namespace must be null, not an empty string. Otherwise the
+ // runtime
+ // complains.
+ addString(node->namespaceUri, kLowPriority, &flatElem->ns,
+ true /* treatEmptyStringAsNull */);
+ addString(node->name, kLowPriority, &flatElem->name,
+ true /* treatEmptyStringAsNull */);
+
+ flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem));
+ flatElem->attributeSize =
+ util::hostToDevice16(sizeof(ResXMLTree_attribute));
+
+ writeAttributes(node, flatElem, &startWriter);
+
+ startWriter.finish();
}
- void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
- mStringRefs.push_back(StringFlattenDest{ ref, dest });
- }
-
- void writeNamespace(xml::Namespace* node, uint16_t type) {
- ChunkWriter writer(mBuffer);
-
- ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type);
- flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
- flatNode->comment.index = util::hostToDevice32(-1);
-
- ResXMLTree_namespaceExt* flatNs = writer.nextBlock<ResXMLTree_namespaceExt>();
- addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
- addString(node->namespaceUri, kLowPriority, &flatNs->uri);
-
- writer.finish();
- }
-
- void visit(xml::Namespace* node) override {
- if (node->namespaceUri == xml::kSchemaTools) {
- // Skip dedicated tools namespace.
- xml::Visitor::visit(node);
- } else {
- writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
- xml::Visitor::visit(node);
- writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
- }
- }
-
- void visit(xml::Text* node) override {
- if (util::trimWhitespace(node->text).empty()) {
- // Skip whitespace only text nodes.
- return;
- }
-
- ChunkWriter writer(mBuffer);
- ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
- flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
- flatNode->comment.index = util::hostToDevice32(-1);
-
- ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>();
- addString(node->text, kLowPriority, &flatText->data);
-
- writer.finish();
- }
-
- void visit(xml::Element* node) override {
- {
- ChunkWriter startWriter(mBuffer);
- ResXMLTree_node* flatNode = startWriter.startChunk<ResXMLTree_node>(
- RES_XML_START_ELEMENT_TYPE);
- flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
- flatNode->comment.index = util::hostToDevice32(-1);
-
- ResXMLTree_attrExt* flatElem = startWriter.nextBlock<ResXMLTree_attrExt>();
-
- // A missing namespace must be null, not an empty string. Otherwise the runtime
- // complains.
- addString(node->namespaceUri, kLowPriority, &flatElem->ns,
- true /* treatEmptyStringAsNull */);
- addString(node->name, kLowPriority, &flatElem->name,
- true /* treatEmptyStringAsNull */);
-
- flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem));
- flatElem->attributeSize = util::hostToDevice16(sizeof(ResXMLTree_attribute));
-
- writeAttributes(node, flatElem, &startWriter);
-
- startWriter.finish();
- }
-
- xml::Visitor::visit(node);
-
- {
- ChunkWriter endWriter(mBuffer);
- ResXMLTree_node* flatEndNode = endWriter.startChunk<ResXMLTree_node>(
- RES_XML_END_ELEMENT_TYPE);
- flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber);
- flatEndNode->comment.index = util::hostToDevice32(-1);
-
- ResXMLTree_endElementExt* flatEndElem = endWriter.nextBlock<ResXMLTree_endElementExt>();
- addString(node->namespaceUri, kLowPriority, &flatEndElem->ns,
- true /* treatEmptyStringAsNull */);
- addString(node->name, kLowPriority, &flatEndElem->name);
-
- endWriter.finish();
- }
- }
-
- static bool cmpXmlAttributeById(const xml::Attribute* a, const xml::Attribute* b) {
- if (a->compiledAttribute && a->compiledAttribute.value().id) {
- if (b->compiledAttribute && b->compiledAttribute.value().id) {
- return a->compiledAttribute.value().id.value() < b->compiledAttribute.value().id.value();
- }
- return true;
- } else if (!b->compiledAttribute) {
- int diff = a->namespaceUri.compare(b->namespaceUri);
- if (diff < 0) {
- return true;
- } else if (diff > 0) {
- return false;
- }
- return a->name < b->name;
- }
- return false;
- }
-
- void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem, ChunkWriter* writer) {
- mFilteredAttrs.clear();
- mFilteredAttrs.reserve(node->attributes.size());
-
- // Filter the attributes.
- for (xml::Attribute& attr : node->attributes) {
- if (mOptions.maxSdkLevel && attr.compiledAttribute && attr.compiledAttribute.value().id) {
- size_t sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id.value());
- if (sdkLevel > mOptions.maxSdkLevel.value()) {
- continue;
- }
- }
- if (attr.namespaceUri == xml::kSchemaTools) {
- continue;
- }
- mFilteredAttrs.push_back(&attr);
- }
-
- if (mFilteredAttrs.empty()) {
- return;
- }
-
- const ResourceId kIdAttr(0x010100d0);
-
- std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(), cmpXmlAttributeById);
-
- flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size());
-
- ResXMLTree_attribute* flatAttr = writer->nextBlock<ResXMLTree_attribute>(
- mFilteredAttrs.size());
- uint16_t attributeIndex = 1;
- for (const xml::Attribute* xmlAttr : mFilteredAttrs) {
- // Assign the indices for specific attributes.
- if (xmlAttr->compiledAttribute && xmlAttr->compiledAttribute.value().id &&
- xmlAttr->compiledAttribute.value().id.value() == kIdAttr) {
- flatElem->idIndex = util::hostToDevice16(attributeIndex);
- } else if (xmlAttr->namespaceUri.empty()) {
- if (xmlAttr->name == "class") {
- flatElem->classIndex = util::hostToDevice16(attributeIndex);
- } else if (xmlAttr->name == "style") {
- flatElem->styleIndex = util::hostToDevice16(attributeIndex);
- }
- }
- attributeIndex++;
-
- // Add the namespaceUri to the list of StringRefs to encode. Use null if the namespace
- // is empty (doesn't exist).
- addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns,
- true /* treatEmptyStringAsNull */);
-
- flatAttr->rawValue.index = util::hostToDevice32(-1);
-
- if (!xmlAttr->compiledAttribute || !xmlAttr->compiledAttribute.value().id) {
- // The attribute has no associated ResourceID, so the string order doesn't matter.
- addString(xmlAttr->name, kLowPriority, &flatAttr->name);
- } else {
- // Attribute names are stored without packages, but we use
- // their StringPool index to lookup their resource IDs.
- // This will cause collisions, so we can't dedupe
- // attribute names from different packages. We use separate
- // pools that we later combine.
- //
- // Lookup the StringPool for this package and make the reference there.
- const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value();
-
- StringPool::Ref nameRef = mPackagePools[aaptAttr.id.value().packageId()].makeRef(
- xmlAttr->name, StringPool::Context{ aaptAttr.id.value().id });
-
- // Add it to the list of strings to flatten.
- addString(nameRef, &flatAttr->name);
- }
-
- if (mOptions.keepRawValues || !xmlAttr->compiledValue) {
- // Keep raw values if the value is not compiled or
- // if we're building a static library (need symbols).
- addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue);
- }
-
- if (xmlAttr->compiledValue) {
- bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue);
- assert(result);
- } else {
- // Flatten as a regular string type.
- flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
- addString(xmlAttr->value, kLowPriority,
- (ResStringPool_ref*) &flatAttr->typedValue.data);
- }
-
- flatAttr->typedValue.size = util::hostToDevice16(sizeof(flatAttr->typedValue));
- flatAttr++;
- }
- }
-};
-
-} // namespace
-
-bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) {
- BigBuffer nodeBuffer(1024);
- XmlFlattenerVisitor visitor(&nodeBuffer, mOptions);
- node->accept(&visitor);
-
- // Merge the package pools into the main pool.
- for (auto& packagePoolEntry : visitor.mPackagePools) {
- visitor.mPool.merge(std::move(packagePoolEntry.second));
- }
-
- // Sort the string pool so that attribute resource IDs show up first.
- visitor.mPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
- return a.context.priority < b.context.priority;
- });
-
- // Now we flatten the string pool references into the correct places.
- for (const auto& refEntry : visitor.mStringRefs) {
- refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex());
- }
-
- // Write the XML header.
- ChunkWriter xmlHeaderWriter(mBuffer);
- xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE);
-
- // Flatten the StringPool.
- StringPool::flattenUtf8(mBuffer, visitor.mPool);
+ xml::Visitor::visit(node);
{
- // Write the array of resource IDs, indexed by StringPool order.
- ChunkWriter resIdMapWriter(mBuffer);
- resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
- for (const auto& str : visitor.mPool) {
- ResourceId id = { str->context.priority };
- if (id.id == kLowPriority || !id.isValid()) {
- // When we see the first non-resource ID,
- // we're done.
- break;
- }
+ ChunkWriter endWriter(mBuffer);
+ ResXMLTree_node* flatEndNode =
+ endWriter.startChunk<ResXMLTree_node>(RES_XML_END_ELEMENT_TYPE);
+ flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber);
+ flatEndNode->comment.index = util::hostToDevice32(-1);
- *resIdMapWriter.nextBlock<uint32_t>() = id.id;
+ ResXMLTree_endElementExt* flatEndElem =
+ endWriter.nextBlock<ResXMLTree_endElementExt>();
+ addString(node->namespaceUri, kLowPriority, &flatEndElem->ns,
+ true /* treatEmptyStringAsNull */);
+ addString(node->name, kLowPriority, &flatEndElem->name);
+
+ endWriter.finish();
+ }
+ }
+
+ static bool cmpXmlAttributeById(const xml::Attribute* a,
+ const xml::Attribute* b) {
+ if (a->compiledAttribute && a->compiledAttribute.value().id) {
+ if (b->compiledAttribute && b->compiledAttribute.value().id) {
+ return a->compiledAttribute.value().id.value() <
+ b->compiledAttribute.value().id.value();
+ }
+ return true;
+ } else if (!b->compiledAttribute) {
+ int diff = a->namespaceUri.compare(b->namespaceUri);
+ if (diff < 0) {
+ return true;
+ } else if (diff > 0) {
+ return false;
+ }
+ return a->name < b->name;
+ }
+ return false;
+ }
+
+ void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem,
+ ChunkWriter* writer) {
+ mFilteredAttrs.clear();
+ mFilteredAttrs.reserve(node->attributes.size());
+
+ // Filter the attributes.
+ for (xml::Attribute& attr : node->attributes) {
+ if (mOptions.maxSdkLevel && attr.compiledAttribute &&
+ attr.compiledAttribute.value().id) {
+ size_t sdkLevel =
+ findAttributeSdkLevel(attr.compiledAttribute.value().id.value());
+ if (sdkLevel > mOptions.maxSdkLevel.value()) {
+ continue;
}
- resIdMapWriter.finish();
+ }
+ if (attr.namespaceUri == xml::kSchemaTools) {
+ continue;
+ }
+ mFilteredAttrs.push_back(&attr);
}
- // Move the nodeBuffer and append it to the out buffer.
- mBuffer->appendBuffer(std::move(nodeBuffer));
+ if (mFilteredAttrs.empty()) {
+ return;
+ }
- // Finish the xml header.
- xmlHeaderWriter.finish();
- return true;
+ const ResourceId kIdAttr(0x010100d0);
+
+ std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(),
+ cmpXmlAttributeById);
+
+ flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size());
+
+ ResXMLTree_attribute* flatAttr =
+ writer->nextBlock<ResXMLTree_attribute>(mFilteredAttrs.size());
+ uint16_t attributeIndex = 1;
+ for (const xml::Attribute* xmlAttr : mFilteredAttrs) {
+ // Assign the indices for specific attributes.
+ if (xmlAttr->compiledAttribute && xmlAttr->compiledAttribute.value().id &&
+ xmlAttr->compiledAttribute.value().id.value() == kIdAttr) {
+ flatElem->idIndex = util::hostToDevice16(attributeIndex);
+ } else if (xmlAttr->namespaceUri.empty()) {
+ if (xmlAttr->name == "class") {
+ flatElem->classIndex = util::hostToDevice16(attributeIndex);
+ } else if (xmlAttr->name == "style") {
+ flatElem->styleIndex = util::hostToDevice16(attributeIndex);
+ }
+ }
+ attributeIndex++;
+
+ // Add the namespaceUri to the list of StringRefs to encode. Use null if
+ // the namespace
+ // is empty (doesn't exist).
+ addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns,
+ true /* treatEmptyStringAsNull */);
+
+ flatAttr->rawValue.index = util::hostToDevice32(-1);
+
+ if (!xmlAttr->compiledAttribute ||
+ !xmlAttr->compiledAttribute.value().id) {
+ // The attribute has no associated ResourceID, so the string order
+ // doesn't matter.
+ addString(xmlAttr->name, kLowPriority, &flatAttr->name);
+ } else {
+ // Attribute names are stored without packages, but we use
+ // their StringPool index to lookup their resource IDs.
+ // This will cause collisions, so we can't dedupe
+ // attribute names from different packages. We use separate
+ // pools that we later combine.
+ //
+ // Lookup the StringPool for this package and make the reference there.
+ const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value();
+
+ StringPool::Ref nameRef =
+ mPackagePools[aaptAttr.id.value().packageId()].makeRef(
+ xmlAttr->name, StringPool::Context(aaptAttr.id.value().id));
+
+ // Add it to the list of strings to flatten.
+ addString(nameRef, &flatAttr->name);
+ }
+
+ if (mOptions.keepRawValues || !xmlAttr->compiledValue) {
+ // Keep raw values if the value is not compiled or
+ // if we're building a static library (need symbols).
+ addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue);
+ }
+
+ if (xmlAttr->compiledValue) {
+ bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue);
+ assert(result);
+ } else {
+ // Flatten as a regular string type.
+ flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
+ addString(xmlAttr->value, kLowPriority,
+ (ResStringPool_ref*)&flatAttr->typedValue.data);
+ }
+
+ flatAttr->typedValue.size =
+ util::hostToDevice16(sizeof(flatAttr->typedValue));
+ flatAttr++;
+ }
+ }
+};
+
+} // namespace
+
+bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) {
+ BigBuffer nodeBuffer(1024);
+ XmlFlattenerVisitor visitor(&nodeBuffer, mOptions);
+ node->accept(&visitor);
+
+ // Merge the package pools into the main pool.
+ for (auto& packagePoolEntry : visitor.mPackagePools) {
+ visitor.mPool.merge(std::move(packagePoolEntry.second));
+ }
+
+ // Sort the string pool so that attribute resource IDs show up first.
+ visitor.mPool.sort(
+ [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+ return a.context.priority < b.context.priority;
+ });
+
+ // Now we flatten the string pool references into the correct places.
+ for (const auto& refEntry : visitor.mStringRefs) {
+ refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex());
+ }
+
+ // Write the XML header.
+ ChunkWriter xmlHeaderWriter(mBuffer);
+ xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE);
+
+ // Flatten the StringPool.
+ StringPool::flattenUtf8(mBuffer, visitor.mPool);
+
+ {
+ // Write the array of resource IDs, indexed by StringPool order.
+ ChunkWriter resIdMapWriter(mBuffer);
+ resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
+ for (const auto& str : visitor.mPool) {
+ ResourceId id = {str->context.priority};
+ if (id.id == kLowPriority || !id.isValid()) {
+ // When we see the first non-resource ID,
+ // we're done.
+ break;
+ }
+
+ *resIdMapWriter.nextBlock<uint32_t>() = id.id;
+ }
+ resIdMapWriter.finish();
+ }
+
+ // Move the nodeBuffer and append it to the out buffer.
+ mBuffer->appendBuffer(std::move(nodeBuffer));
+
+ // Finish the xml header.
+ xmlHeaderWriter.finish();
+ return true;
}
bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) {
- if (!resource->root) {
- return false;
- }
- return flatten(context, resource->root.get());
+ if (!resource->root) {
+ return false;
+ }
+ return flatten(context, resource->root.get());
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h
index a688ac9..d8d592b 100644
--- a/tools/aapt2/flatten/XmlFlattener.h
+++ b/tools/aapt2/flatten/XmlFlattener.h
@@ -24,32 +24,31 @@
namespace aapt {
struct XmlFlattenerOptions {
- /**
- * Keep attribute raw string values along with typed values.
- */
- bool keepRawValues = false;
+ /**
+ * Keep attribute raw string values along with typed values.
+ */
+ bool keepRawValues = false;
- /**
- * If set, the max SDK level of attribute to flatten. All others are ignored.
- */
- Maybe<size_t> maxSdkLevel;
+ /**
+ * If set, the max SDK level of attribute to flatten. All others are ignored.
+ */
+ Maybe<size_t> maxSdkLevel;
};
class XmlFlattener : public IXmlResourceConsumer {
-public:
- XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) :
- mBuffer(buffer), mOptions(options) {
- }
+ public:
+ XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options)
+ : mBuffer(buffer), mOptions(options) {}
- bool consume(IAaptContext* context, xml::XmlResource* resource) override;
+ bool consume(IAaptContext* context, xml::XmlResource* resource) override;
-private:
- BigBuffer* mBuffer;
- XmlFlattenerOptions mOptions;
+ private:
+ BigBuffer* mBuffer;
+ XmlFlattenerOptions mOptions;
- bool flatten(IAaptContext* context, xml::Node* node);
+ bool flatten(IAaptContext* context, xml::Node* node);
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_FLATTEN_XMLFLATTENER_H */
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
index 4d1e178..e0159cb 100644
--- a/tools/aapt2/flatten/XmlFlattener_test.cpp
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -25,230 +25,240 @@
namespace aapt {
class XmlFlattenerTest : public ::testing::Test {
-public:
- void SetUp() override {
- mContext = test::ContextBuilder()
- .setCompilationPackage("com.app.test")
- .setNameManglerPolicy(NameManglerPolicy{ "com.app.test" })
- .addSymbolSource(test::StaticSymbolSourceBuilder()
- .addSymbol("android:attr/id", ResourceId(0x010100d0),
- test::AttributeBuilder().build())
- .addSymbol("com.app.test:id/id", ResourceId(0x7f020000))
- .addSymbol("android:attr/paddingStart", ResourceId(0x010103b3),
- test::AttributeBuilder().build())
- .addSymbol("android:attr/colorAccent", ResourceId(0x01010435),
- test::AttributeBuilder().build())
- .build())
- .build();
+ public:
+ void SetUp() override {
+ mContext =
+ test::ContextBuilder()
+ .setCompilationPackage("com.app.test")
+ .setNameManglerPolicy(NameManglerPolicy{"com.app.test"})
+ .addSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .addSymbol("android:attr/id", ResourceId(0x010100d0),
+ test::AttributeBuilder().build())
+ .addSymbol("com.app.test:id/id", ResourceId(0x7f020000))
+ .addSymbol("android:attr/paddingStart",
+ ResourceId(0x010103b3),
+ test::AttributeBuilder().build())
+ .addSymbol("android:attr/colorAccent",
+ ResourceId(0x01010435),
+ test::AttributeBuilder().build())
+ .build())
+ .build();
+ }
+
+ ::testing::AssertionResult flatten(xml::XmlResource* doc,
+ android::ResXMLTree* outTree,
+ const XmlFlattenerOptions& options = {}) {
+ using namespace android; // For NO_ERROR on windows because it is a macro.
+
+ BigBuffer buffer(1024);
+ XmlFlattener flattener(&buffer, options);
+ if (!flattener.consume(mContext.get(), doc)) {
+ return ::testing::AssertionFailure() << "failed to flatten XML Tree";
}
- ::testing::AssertionResult flatten(xml::XmlResource* doc, android::ResXMLTree* outTree,
- const XmlFlattenerOptions& options = {}) {
- using namespace android; // For NO_ERROR on windows because it is a macro.
-
- BigBuffer buffer(1024);
- XmlFlattener flattener(&buffer, options);
- if (!flattener.consume(mContext.get(), doc)) {
- return ::testing::AssertionFailure() << "failed to flatten XML Tree";
- }
-
- std::unique_ptr<uint8_t[]> data = util::copy(buffer);
- if (outTree->setTo(data.get(), buffer.size(), true) != NO_ERROR) {
- return ::testing::AssertionFailure() << "flattened XML is corrupt";
- }
- return ::testing::AssertionSuccess();
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ if (outTree->setTo(data.get(), buffer.size(), true) != NO_ERROR) {
+ return ::testing::AssertionFailure() << "flattened XML is corrupt";
}
+ return ::testing::AssertionSuccess();
+ }
-protected:
- std::unique_ptr<IAaptContext> mContext;
+ protected:
+ std::unique_ptr<IAaptContext> mContext;
};
TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:test="http://com.test"
attr="hey">
<Layout test:hello="hi" />
<Layout>Some text</Layout>
</View>)EOF");
+ android::ResXMLTree tree;
+ ASSERT_TRUE(flatten(doc.get(), &tree));
- android::ResXMLTree tree;
- ASSERT_TRUE(flatten(doc.get(), &tree));
+ ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
- ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
+ size_t len;
+ const char16_t* namespacePrefix = tree.getNamespacePrefix(&len);
+ EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
- size_t len;
- const char16_t* namespacePrefix = tree.getNamespacePrefix(&len);
- EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
+ const char16_t* namespaceUri = tree.getNamespaceUri(&len);
+ ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
- const char16_t* namespaceUri = tree.getNamespaceUri(&len);
- ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
+ ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
- ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+ ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+ const char16_t* tagName = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tagName, len), u"View");
- ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
- const char16_t* tagName = tree.getElementName(&len);
- EXPECT_EQ(StringPiece16(tagName, len), u"View");
+ ASSERT_EQ(1u, tree.getAttributeCount());
+ ASSERT_EQ(tree.getAttributeNamespace(0, &len), nullptr);
+ const char16_t* attrName = tree.getAttributeName(0, &len);
+ EXPECT_EQ(StringPiece16(attrName, len), u"attr");
- ASSERT_EQ(1u, tree.getAttributeCount());
- ASSERT_EQ(tree.getAttributeNamespace(0, &len), nullptr);
- const char16_t* attrName = tree.getAttributeName(0, &len);
- EXPECT_EQ(StringPiece16(attrName, len), u"attr");
+ EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr",
+ StringPiece16(u"attr").size()));
- EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size()));
+ ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
- ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+ ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+ tagName = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
- ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
- tagName = tree.getElementName(&len);
- EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
+ ASSERT_EQ(1u, tree.getAttributeCount());
+ const char16_t* attrNamespace = tree.getAttributeNamespace(0, &len);
+ EXPECT_EQ(StringPiece16(attrNamespace, len), u"http://com.test");
- ASSERT_EQ(1u, tree.getAttributeCount());
- const char16_t* attrNamespace = tree.getAttributeNamespace(0, &len);
- EXPECT_EQ(StringPiece16(attrNamespace, len), u"http://com.test");
+ attrName = tree.getAttributeName(0, &len);
+ EXPECT_EQ(StringPiece16(attrName, len), u"hello");
- attrName = tree.getAttributeName(0, &len);
- EXPECT_EQ(StringPiece16(attrName, len), u"hello");
+ ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
+ ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
- ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
- ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+ ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+ tagName = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
+ ASSERT_EQ(0u, tree.getAttributeCount());
- ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
- tagName = tree.getElementName(&len);
- EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
- ASSERT_EQ(0u, tree.getAttributeCount());
+ ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT);
+ const char16_t* text = tree.getText(&len);
+ EXPECT_EQ(StringPiece16(text, len), u"Some text");
- ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT);
- const char16_t* text = tree.getText(&len);
- EXPECT_EQ(StringPiece16(text, len), u"Some text");
+ ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
+ ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+ tagName = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
- ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
- ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
- tagName = tree.getElementName(&len);
- EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
+ ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
+ ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
+ tagName = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tagName, len), u"View");
- ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
- ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
- tagName = tree.getElementName(&len);
- EXPECT_EQ(StringPiece16(tagName, len), u"View");
+ ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE);
+ namespacePrefix = tree.getNamespacePrefix(&len);
+ EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
- ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE);
- namespacePrefix = tree.getNamespacePrefix(&len);
- EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
+ namespaceUri = tree.getNamespaceUri(&len);
+ ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
- namespaceUri = tree.getNamespaceUri(&len);
- ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
-
- ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT);
+ ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT);
}
TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:paddingStart="1dp"
android:colorAccent="#ffffff"/>)EOF");
- XmlReferenceLinker linker;
- ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- ASSERT_TRUE(linker.getSdkLevels().count(17) == 1);
- ASSERT_TRUE(linker.getSdkLevels().count(21) == 1);
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+ ASSERT_TRUE(linker.getSdkLevels().count(17) == 1);
+ ASSERT_TRUE(linker.getSdkLevels().count(21) == 1);
- android::ResXMLTree tree;
- XmlFlattenerOptions options;
- options.maxSdkLevel = 17;
- ASSERT_TRUE(flatten(doc.get(), &tree, options));
+ android::ResXMLTree tree;
+ XmlFlattenerOptions options;
+ options.maxSdkLevel = 17;
+ ASSERT_TRUE(flatten(doc.get(), &tree, options));
- while (tree.next() != android::ResXMLTree::START_TAG) {
- ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
- ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
- }
+ while (tree.next() != android::ResXMLTree::START_TAG) {
+ ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
+ ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
+ }
- ASSERT_EQ(1u, tree.getAttributeCount());
- EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0));
+ ASSERT_EQ(1u, tree.getAttributeCount());
+ EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0));
}
TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:tools="http://schemas.android.com/tools"
xmlns:foo="http://schemas.android.com/foo"
foo:bar="Foo"
tools:ignore="MissingTranslation"/>)EOF");
- android::ResXMLTree tree;
- ASSERT_TRUE(flatten(doc.get(), &tree));
+ android::ResXMLTree tree;
+ ASSERT_TRUE(flatten(doc.get(), &tree));
- ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
+ ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
- size_t len;
- const char16_t* namespacePrefix = tree.getNamespacePrefix(&len);
- EXPECT_EQ(StringPiece16(namespacePrefix, len), u"foo");
+ size_t len;
+ const char16_t* namespacePrefix = tree.getNamespacePrefix(&len);
+ EXPECT_EQ(StringPiece16(namespacePrefix, len), u"foo");
- const char16_t* namespaceUri = tree.getNamespaceUri(&len);
- ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://schemas.android.com/foo");
+ const char16_t* namespaceUri = tree.getNamespaceUri(&len);
+ ASSERT_EQ(StringPiece16(namespaceUri, len),
+ u"http://schemas.android.com/foo");
- ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
+ ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
- EXPECT_EQ(
- tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"),
+ EXPECT_EQ(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"),
android::NAME_NOT_FOUND);
- EXPECT_GE(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), 0);
+ EXPECT_GE(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), 0);
}
TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@id/id"
class="str"
style="@id/id"/>)EOF");
- android::ResXMLTree tree;
- ASSERT_TRUE(flatten(doc.get(), &tree));
+ android::ResXMLTree tree;
+ ASSERT_TRUE(flatten(doc.get(), &tree));
- while (tree.next() != android::ResXMLTree::START_TAG) {
- ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
- ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
- }
+ while (tree.next() != android::ResXMLTree::START_TAG) {
+ ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
+ ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
+ }
- EXPECT_EQ(tree.indexOfClass(), 0);
- EXPECT_EQ(tree.indexOfStyle(), 1);
+ EXPECT_EQ(tree.indexOfClass(), 0);
+ EXPECT_EQ(tree.indexOfStyle(), 1);
}
/*
- * The device ResXMLParser in libandroidfw differentiates between empty namespace and null
+ * The device ResXMLParser in libandroidfw differentiates between empty
+ * namespace and null
* namespace.
*/
TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>");
+ std::unique_ptr<xml::XmlResource> doc =
+ test::buildXmlDom("<View package=\"android\"/>");
- android::ResXMLTree tree;
- ASSERT_TRUE(flatten(doc.get(), &tree));
+ android::ResXMLTree tree;
+ ASSERT_TRUE(flatten(doc.get(), &tree));
- while (tree.next() != android::ResXMLTree::START_TAG) {
- ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
- ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
- }
+ while (tree.next() != android::ResXMLTree::START_TAG) {
+ ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
+ ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
+ }
- const StringPiece16 kPackage = u"package";
- EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
+ const StringPiece16 kPackage = u"package";
+ EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()),
+ 0);
}
TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View package=\"\"/>");
+ std::unique_ptr<xml::XmlResource> doc =
+ test::buildXmlDom("<View package=\"\"/>");
- android::ResXMLTree tree;
- ASSERT_TRUE(flatten(doc.get(), &tree));
+ android::ResXMLTree tree;
+ ASSERT_TRUE(flatten(doc.get(), &tree));
- while (tree.next() != android::ResXMLTree::START_TAG) {
- ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
- ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
- }
+ while (tree.next() != android::ResXMLTree::START_TAG) {
+ ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
+ ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
+ }
- const StringPiece16 kPackage = u"package";
- ssize_t idx = tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
- ASSERT_GE(idx, 0);
+ const StringPiece16 kPackage = u"package";
+ ssize_t idx =
+ tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
+ ASSERT_GE(idx, 0);
- size_t len;
- EXPECT_NE(nullptr, tree.getAttributeStringValue(idx, &len));
+ size_t len;
+ EXPECT_NE(nullptr, tree.getAttributeStringValue(idx, &len));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
index 34eed63..0479228b 100644
--- a/tools/aapt2/io/Data.h
+++ b/tools/aapt2/io/Data.h
@@ -18,105 +18,95 @@
#define AAPT_IO_DATA_H
#include <android-base/macros.h>
-#include <memory>
#include <utils/FileMap.h>
+#include <memory>
namespace aapt {
namespace io {
/**
- * Interface for a block of contiguous memory. An instance of this interface owns the data.
+ * Interface for a block of contiguous memory. An instance of this interface
+ * owns the data.
*/
class IData {
-public:
- virtual ~IData() = default;
+ public:
+ virtual ~IData() = default;
- virtual const void* data() const = 0;
- virtual size_t size() const = 0;
+ virtual const void* data() const = 0;
+ virtual size_t size() const = 0;
};
class DataSegment : public IData {
-public:
- explicit DataSegment(std::unique_ptr<IData> data, size_t offset, size_t len) :
- mData(std::move(data)), mOffset(offset), mLen(len) {
- }
+ public:
+ explicit DataSegment(std::unique_ptr<IData> data, size_t offset, size_t len)
+ : mData(std::move(data)), mOffset(offset), mLen(len) {}
- const void* data() const override {
- return static_cast<const uint8_t*>(mData->data()) + mOffset;
- }
+ const void* data() const override {
+ return static_cast<const uint8_t*>(mData->data()) + mOffset;
+ }
- size_t size() const override {
- return mLen;
- }
+ size_t size() const override { return mLen; }
-private:
- DISALLOW_COPY_AND_ASSIGN(DataSegment);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DataSegment);
- std::unique_ptr<IData> mData;
- size_t mOffset;
- size_t mLen;
+ std::unique_ptr<IData> mData;
+ size_t mOffset;
+ size_t mLen;
};
/**
- * Implementation of IData that exposes a memory mapped file. The mmapped file is owned by this
+ * Implementation of IData that exposes a memory mapped file. The mmapped file
+ * is owned by this
* object.
*/
class MmappedData : public IData {
-public:
- explicit MmappedData(android::FileMap&& map) : mMap(std::forward<android::FileMap>(map)) {
- }
+ public:
+ explicit MmappedData(android::FileMap&& map)
+ : mMap(std::forward<android::FileMap>(map)) {}
- const void* data() const override {
- return mMap.getDataPtr();
- }
+ const void* data() const override { return mMap.getDataPtr(); }
- size_t size() const override {
- return mMap.getDataLength();
- }
+ size_t size() const override { return mMap.getDataLength(); }
-private:
- android::FileMap mMap;
+ private:
+ android::FileMap mMap;
};
/**
- * Implementation of IData that exposes a block of memory that was malloc'ed (new'ed). The
+ * Implementation of IData that exposes a block of memory that was malloc'ed
+ * (new'ed). The
* memory is owned by this object.
*/
class MallocData : public IData {
-public:
- MallocData(std::unique_ptr<const uint8_t[]> data, size_t size) :
- mData(std::move(data)), mSize(size) {
- }
+ public:
+ MallocData(std::unique_ptr<const uint8_t[]> data, size_t size)
+ : mData(std::move(data)), mSize(size) {}
- const void* data() const override {
- return mData.get();
- }
+ const void* data() const override { return mData.get(); }
- size_t size() const override {
- return mSize;
- }
+ size_t size() const override { return mSize; }
-private:
- std::unique_ptr<const uint8_t[]> mData;
- size_t mSize;
+ private:
+ std::unique_ptr<const uint8_t[]> mData;
+ size_t mSize;
};
/**
- * When mmap fails because the file has length 0, we use the EmptyData to simulate data of length 0.
+ * When mmap fails because the file has length 0, we use the EmptyData to
+ * simulate data of length 0.
*/
class EmptyData : public IData {
-public:
- const void* data() const override {
- static const uint8_t d = 0;
- return &d;
- }
+ public:
+ const void* data() const override {
+ static const uint8_t d = 0;
+ return &d;
+ }
- size_t size() const override {
- return 0u;
- }
+ size_t size() const override { return 0u; }
};
-} // namespace io
-} // namespace aapt
+} // namespace io
+} // namespace aapt
#endif /* AAPT_IO_DATA_H */
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 807981e..012f446 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -30,83 +30,90 @@
namespace io {
/**
- * Interface for a file, which could be a real file on the file system, or a file inside
+ * Interface for a file, which could be a real file on the file system, or a
+ * file inside
* a ZIP archive.
*/
class IFile {
-public:
- virtual ~IFile() = default;
+ public:
+ virtual ~IFile() = default;
- /**
- * Open the file and return it as a block of contiguous memory. How this occurs is
- * implementation dependent. For example, if this is a file on the file system, it may
- * simply mmap the contents. If this file represents a compressed file in a ZIP archive,
- * it may need to inflate it to memory, incurring a copy.
- *
- * Returns nullptr on failure.
- */
- virtual std::unique_ptr<IData> openAsData() = 0;
+ /**
+ * Open the file and return it as a block of contiguous memory. How this
+ * occurs is
+ * implementation dependent. For example, if this is a file on the file
+ * system, it may
+ * simply mmap the contents. If this file represents a compressed file in a
+ * ZIP archive,
+ * it may need to inflate it to memory, incurring a copy.
+ *
+ * Returns nullptr on failure.
+ */
+ virtual std::unique_ptr<IData> openAsData() = 0;
- /**
- * Returns the source of this file. This is for presentation to the user and may not be a
- * valid file system path (for example, it may contain a '@' sign to separate the files within
- * a ZIP archive from the path to the containing ZIP archive.
- */
- virtual const Source& getSource() const = 0;
+ /**
+ * Returns the source of this file. This is for presentation to the user and
+ * may not be a
+ * valid file system path (for example, it may contain a '@' sign to separate
+ * the files within
+ * a ZIP archive from the path to the containing ZIP archive.
+ */
+ virtual const Source& getSource() const = 0;
- IFile* createFileSegment(size_t offset, size_t len);
+ IFile* createFileSegment(size_t offset, size_t len);
-private:
- // Any segments created from this IFile need to be owned by this IFile, so keep them
- // in a list. This will never be read, so we prefer better insertion performance
- // than cache locality, hence the list.
- std::list<std::unique_ptr<IFile>> mSegments;
+ private:
+ // Any segments created from this IFile need to be owned by this IFile, so
+ // keep them
+ // in a list. This will never be read, so we prefer better insertion
+ // performance
+ // than cache locality, hence the list.
+ std::list<std::unique_ptr<IFile>> mSegments;
};
/**
- * An IFile that wraps an underlying IFile but limits it to a subsection of that file.
+ * An IFile that wraps an underlying IFile but limits it to a subsection of that
+ * file.
*/
class FileSegment : public IFile {
-public:
- explicit FileSegment(IFile* file, size_t offset, size_t len) :
- mFile(file), mOffset(offset), mLen(len) {
- }
+ public:
+ explicit FileSegment(IFile* file, size_t offset, size_t len)
+ : mFile(file), mOffset(offset), mLen(len) {}
- std::unique_ptr<IData> openAsData() override;
+ std::unique_ptr<IData> openAsData() override;
- const Source& getSource() const override {
- return mFile->getSource();
- }
+ const Source& getSource() const override { return mFile->getSource(); }
-private:
- DISALLOW_COPY_AND_ASSIGN(FileSegment);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileSegment);
- IFile* mFile;
- size_t mOffset;
- size_t mLen;
+ IFile* mFile;
+ size_t mOffset;
+ size_t mLen;
};
class IFileCollectionIterator {
-public:
- virtual ~IFileCollectionIterator() = default;
+ public:
+ virtual ~IFileCollectionIterator() = default;
- virtual bool hasNext() = 0;
- virtual IFile* next() = 0;
+ virtual bool hasNext() = 0;
+ virtual IFile* next() = 0;
};
/**
- * Interface for a collection of files, all of which share a common source. That source may
+ * Interface for a collection of files, all of which share a common source. That
+ * source may
* simply be the filesystem, or a ZIP archive.
*/
class IFileCollection {
-public:
- virtual ~IFileCollection() = default;
+ public:
+ virtual ~IFileCollection() = default;
- virtual IFile* findFile(const StringPiece& path) = 0;
- virtual std::unique_ptr<IFileCollectionIterator> iterator() = 0;
+ virtual IFile* findFile(const StringPiece& path) = 0;
+ virtual std::unique_ptr<IFileCollectionIterator> iterator() = 0;
};
-} // namespace io
-} // namespace aapt
+} // namespace io
+} // namespace aapt
#endif /* AAPT_IO_FILE_H */
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index 72a932a..8584d48 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -28,47 +28,47 @@
* A regular file from the file system. Uses mmap to open the data.
*/
class RegularFile : public IFile {
-public:
- explicit RegularFile(const Source& source);
+ public:
+ explicit RegularFile(const Source& source);
- std::unique_ptr<IData> openAsData() override;
- const Source& getSource() const override;
+ std::unique_ptr<IData> openAsData() override;
+ const Source& getSource() const override;
-private:
- Source mSource;
+ private:
+ Source mSource;
};
class FileCollection;
class FileCollectionIterator : public IFileCollectionIterator {
-public:
- explicit FileCollectionIterator(FileCollection* collection);
+ public:
+ explicit FileCollectionIterator(FileCollection* collection);
- bool hasNext() override;
- io::IFile* next() override;
+ bool hasNext() override;
+ io::IFile* next() override;
-private:
- std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd;
+ private:
+ std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd;
};
/**
* An IFileCollection representing the file system.
*/
class FileCollection : public IFileCollection {
-public:
- /**
- * Adds a file located at path. Returns the IFile representation of that file.
- */
- IFile* insertFile(const StringPiece& path);
- IFile* findFile(const StringPiece& path) override;
- std::unique_ptr<IFileCollectionIterator> iterator() override;
+ public:
+ /**
+ * Adds a file located at path. Returns the IFile representation of that file.
+ */
+ IFile* insertFile(const StringPiece& path);
+ IFile* findFile(const StringPiece& path) override;
+ std::unique_ptr<IFileCollectionIterator> iterator() override;
-private:
- friend class FileCollectionIterator;
- std::map<std::string, std::unique_ptr<IFile>> mFiles;
+ private:
+ friend class FileCollectionIterator;
+ std::map<std::string, std::unique_ptr<IFile>> mFiles;
};
-} // namespace io
-} // namespace aapt
+} // namespace io
+} // namespace aapt
-#endif // AAPT_IO_FILESYSTEM_H
+#endif // AAPT_IO_FILESYSTEM_H
diff --git a/tools/aapt2/io/Io.h b/tools/aapt2/io/Io.h
index e1e9107..49b9fc3 100644
--- a/tools/aapt2/io/Io.h
+++ b/tools/aapt2/io/Io.h
@@ -30,12 +30,10 @@
* The code style here matches the protobuf style.
*/
class InputStream : public google::protobuf::io::ZeroCopyInputStream {
-public:
- virtual std::string GetError() const {
- return {};
- }
+ public:
+ virtual std::string GetError() const { return {}; }
- virtual bool HadError() const = 0;
+ virtual bool HadError() const = 0;
};
/**
@@ -45,12 +43,10 @@
* The code style here matches the protobuf style.
*/
class OutputStream : public google::protobuf::io::ZeroCopyOutputStream {
-public:
- virtual std::string GetError() const {
- return {};
- }
+ public:
+ virtual std::string GetError() const { return {}; }
- virtual bool HadError() const = 0;
+ virtual bool HadError() const = 0;
};
/**
@@ -60,7 +56,7 @@
*/
bool copy(OutputStream* out, InputStream* in);
-} // namespace io
-} // namespace aapt
+} // namespace io
+} // namespace aapt
#endif /* AAPT_IO_IO_H */
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
index 565588e..e04525f 100644
--- a/tools/aapt2/io/ZipArchive.h
+++ b/tools/aapt2/io/ZipArchive.h
@@ -20,64 +20,66 @@
#include "io/File.h"
#include "util/StringPiece.h"
-#include <map>
#include <ziparchive/zip_archive.h>
+#include <map>
namespace aapt {
namespace io {
/**
- * An IFile representing a file within a ZIP archive. If the file is compressed, it is uncompressed
- * and copied into memory when opened. Otherwise it is mmapped from the ZIP archive.
+ * An IFile representing a file within a ZIP archive. If the file is compressed,
+ * it is uncompressed
+ * and copied into memory when opened. Otherwise it is mmapped from the ZIP
+ * archive.
*/
class ZipFile : public IFile {
-public:
- ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source);
+ public:
+ ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source);
- std::unique_ptr<IData> openAsData() override;
- const Source& getSource() const override;
+ std::unique_ptr<IData> openAsData() override;
+ const Source& getSource() const override;
-private:
- ZipArchiveHandle mZipHandle;
- ZipEntry mZipEntry;
- Source mSource;
+ private:
+ ZipArchiveHandle mZipHandle;
+ ZipEntry mZipEntry;
+ Source mSource;
};
class ZipFileCollection;
class ZipFileCollectionIterator : public IFileCollectionIterator {
-public:
- explicit ZipFileCollectionIterator(ZipFileCollection* collection);
+ public:
+ explicit ZipFileCollectionIterator(ZipFileCollection* collection);
- bool hasNext() override;
- io::IFile* next() override;
+ bool hasNext() override;
+ io::IFile* next() override;
-private:
- std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd;
+ private:
+ std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd;
};
/**
* An IFileCollection that represents a ZIP archive and the entries within it.
*/
class ZipFileCollection : public IFileCollection {
-public:
- static std::unique_ptr<ZipFileCollection> create(const StringPiece& path,
- std::string* outError);
+ public:
+ static std::unique_ptr<ZipFileCollection> create(const StringPiece& path,
+ std::string* outError);
- io::IFile* findFile(const StringPiece& path) override;
- std::unique_ptr<IFileCollectionIterator> iterator() override;
+ io::IFile* findFile(const StringPiece& path) override;
+ std::unique_ptr<IFileCollectionIterator> iterator() override;
- ~ZipFileCollection() override;
+ ~ZipFileCollection() override;
-private:
- friend class ZipFileCollectionIterator;
- ZipFileCollection();
+ private:
+ friend class ZipFileCollectionIterator;
+ ZipFileCollection();
- ZipArchiveHandle mHandle;
- std::map<std::string, std::unique_ptr<IFile>> mFiles;
+ ZipArchiveHandle mHandle;
+ std::map<std::string, std::unique_ptr<IFile>> mFiles;
};
-} // namespace io
-} // namespace aapt
+} // namespace io
+} // namespace aapt
#endif /* AAPT_IO_ZIPARCHIVE_H */
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
index cfc32f3..5419608 100644
--- a/tools/aapt2/java/AnnotationProcessor.h
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -52,34 +52,36 @@
*
*/
class AnnotationProcessor {
-public:
- /**
- * Adds more comments. Since resources can have various values with different configurations,
- * we need to collect all the comments.
- */
- void appendComment(const StringPiece& comment);
+ public:
+ /**
+ * Adds more comments. Since resources can have various values with different
+ * configurations,
+ * we need to collect all the comments.
+ */
+ void appendComment(const StringPiece& comment);
- void appendNewLine();
+ void appendNewLine();
- /**
- * Writes the comments and annotations to the stream, with the given prefix before each line.
- */
- void writeToStream(std::ostream* out, const StringPiece& prefix) const;
+ /**
+ * Writes the comments and annotations to the stream, with the given prefix
+ * before each line.
+ */
+ void writeToStream(std::ostream* out, const StringPiece& prefix) const;
-private:
- enum : uint32_t {
- kDeprecated = 0x01,
- kSystemApi = 0x02,
- };
+ private:
+ enum : uint32_t {
+ kDeprecated = 0x01,
+ kSystemApi = 0x02,
+ };
- std::stringstream mComment;
- std::stringstream mAnnotations;
- bool mHasComments = false;
- uint32_t mAnnotationBitMask = 0;
+ std::stringstream mComment;
+ std::stringstream mAnnotations;
+ bool mHasComments = false;
+ uint32_t mAnnotationBitMask = 0;
- void appendCommentLine(std::string& line);
+ void appendCommentLine(std::string& line);
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_JAVA_ANNOTATIONPROCESSOR_H */
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index e84c274..bd7e7b2 100644
--- a/tools/aapt2/java/ClassDefinition.h
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -33,46 +33,43 @@
constexpr static const char* kIndent = " ";
class ClassMember {
-public:
- virtual ~ClassMember() = default;
+ public:
+ virtual ~ClassMember() = default;
- AnnotationProcessor* getCommentBuilder() {
- return &mProcessor;
- }
+ AnnotationProcessor* getCommentBuilder() { return &mProcessor; }
- virtual bool empty() const = 0;
+ virtual bool empty() const = 0;
- virtual void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const {
- mProcessor.writeToStream(out, prefix);
- }
+ virtual void writeToStream(const StringPiece& prefix, bool final,
+ std::ostream* out) const {
+ mProcessor.writeToStream(out, prefix);
+ }
-private:
- AnnotationProcessor mProcessor;
+ private:
+ AnnotationProcessor mProcessor;
};
template <typename T>
class PrimitiveMember : public ClassMember {
-public:
- PrimitiveMember(const StringPiece& name, const T& val) :
- mName(name.toString()), mVal(val) {
- }
+ public:
+ PrimitiveMember(const StringPiece& name, const T& val)
+ : mName(name.toString()), mVal(val) {}
- bool empty() const override {
- return false;
- }
+ bool empty() const override { return false; }
- void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override {
- ClassMember::writeToStream(prefix, final, out);
+ void writeToStream(const StringPiece& prefix, bool final,
+ std::ostream* out) const override {
+ ClassMember::writeToStream(prefix, final, out);
- *out << prefix << "public static " << (final ? "final " : "")
- << "int " << mName << "=" << mVal << ";";
- }
+ *out << prefix << "public static " << (final ? "final " : "") << "int "
+ << mName << "=" << mVal << ";";
+ }
-private:
- std::string mName;
- T mVal;
+ private:
+ std::string mName;
+ T mVal;
- DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
+ DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
};
/**
@@ -80,27 +77,25 @@
*/
template <>
class PrimitiveMember<std::string> : public ClassMember {
-public:
- PrimitiveMember(const StringPiece& name, const std::string& val) :
- mName(name.toString()), mVal(val) {
- }
+ public:
+ PrimitiveMember(const StringPiece& name, const std::string& val)
+ : mName(name.toString()), mVal(val) {}
- bool empty() const override {
- return false;
- }
+ bool empty() const override { return false; }
- void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override {
- ClassMember::writeToStream(prefix, final, out);
+ void writeToStream(const StringPiece& prefix, bool final,
+ std::ostream* out) const override {
+ ClassMember::writeToStream(prefix, final, out);
- *out << prefix << "public static " << (final ? "final " : "")
- << "String " << mName << "=\"" << mVal << "\";";
- }
+ *out << prefix << "public static " << (final ? "final " : "") << "String "
+ << mName << "=\"" << mVal << "\";";
+ }
-private:
- std::string mName;
- std::string mVal;
+ private:
+ std::string mName;
+ std::string mVal;
- DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
+ DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
};
using IntMember = PrimitiveMember<uint32_t>;
@@ -109,80 +104,75 @@
template <typename T>
class PrimitiveArrayMember : public ClassMember {
-public:
- explicit PrimitiveArrayMember(const StringPiece& name) :
- mName(name.toString()) {
+ public:
+ explicit PrimitiveArrayMember(const StringPiece& name)
+ : mName(name.toString()) {}
+
+ void addElement(const T& val) { mElements.push_back(val); }
+
+ bool empty() const override { return false; }
+
+ void writeToStream(const StringPiece& prefix, bool final,
+ std::ostream* out) const override {
+ ClassMember::writeToStream(prefix, final, out);
+
+ *out << prefix << "public static final int[] " << mName << "={";
+
+ const auto begin = mElements.begin();
+ const auto end = mElements.end();
+ for (auto current = begin; current != end; ++current) {
+ if (std::distance(begin, current) % kAttribsPerLine == 0) {
+ *out << "\n" << prefix << kIndent << kIndent;
+ }
+
+ *out << *current;
+ if (std::distance(current, end) > 1) {
+ *out << ", ";
+ }
}
+ *out << "\n" << prefix << kIndent << "};";
+ }
- void addElement(const T& val) {
- mElements.push_back(val);
- }
+ private:
+ std::string mName;
+ std::vector<T> mElements;
- bool empty() const override {
- return false;
- }
-
- void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override {
- ClassMember::writeToStream(prefix, final, out);
-
- *out << prefix << "public static final int[] " << mName << "={";
-
- const auto begin = mElements.begin();
- const auto end = mElements.end();
- for (auto current = begin; current != end; ++current) {
- if (std::distance(begin, current) % kAttribsPerLine == 0) {
- *out << "\n" << prefix << kIndent << kIndent;
- }
-
- *out << *current;
- if (std::distance(current, end) > 1) {
- *out << ", ";
- }
- }
- *out << "\n" << prefix << kIndent <<"};";
- }
-
-private:
- std::string mName;
- std::vector<T> mElements;
-
- DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember);
+ DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember);
};
using ResourceArrayMember = PrimitiveArrayMember<ResourceId>;
-enum class ClassQualifier {
- None,
- Static
-};
+enum class ClassQualifier { None, Static };
class ClassDefinition : public ClassMember {
-public:
- static bool writeJavaFile(const ClassDefinition* def,
- const StringPiece& package,
- bool final,
- std::ostream* out);
+ public:
+ static bool writeJavaFile(const ClassDefinition* def,
+ const StringPiece& package, bool final,
+ std::ostream* out);
- ClassDefinition(const StringPiece& name, ClassQualifier qualifier, bool createIfEmpty) :
- mName(name.toString()), mQualifier(qualifier), mCreateIfEmpty(createIfEmpty) {
- }
+ ClassDefinition(const StringPiece& name, ClassQualifier qualifier,
+ bool createIfEmpty)
+ : mName(name.toString()),
+ mQualifier(qualifier),
+ mCreateIfEmpty(createIfEmpty) {}
- void addMember(std::unique_ptr<ClassMember> member) {
- mMembers.push_back(std::move(member));
- }
+ void addMember(std::unique_ptr<ClassMember> member) {
+ mMembers.push_back(std::move(member));
+ }
- bool empty() const override;
- void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override;
+ bool empty() const override;
+ void writeToStream(const StringPiece& prefix, bool final,
+ std::ostream* out) const override;
-private:
- std::string mName;
- ClassQualifier mQualifier;
- bool mCreateIfEmpty;
- std::vector<std::unique_ptr<ClassMember>> mMembers;
+ private:
+ std::string mName;
+ ClassQualifier mQualifier;
+ bool mCreateIfEmpty;
+ std::vector<std::unique_ptr<ClassMember>> mMembers;
- DISALLOW_COPY_AND_ASSIGN(ClassDefinition);
+ DISALLOW_COPY_AND_ASSIGN(ClassDefinition);
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_JAVA_CLASSDEFINITION_H */
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 901a86e..2fdf268 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -31,72 +31,74 @@
class ClassDefinition;
struct JavaClassGeneratorOptions {
- /*
- * Specifies whether to use the 'final' modifier
- * on resource entries. Default is true.
- */
- bool useFinal = true;
+ /*
+ * Specifies whether to use the 'final' modifier
+ * on resource entries. Default is true.
+ */
+ bool useFinal = true;
- enum class SymbolTypes {
- kAll,
- kPublicPrivate,
- kPublic,
- };
+ enum class SymbolTypes {
+ kAll,
+ kPublicPrivate,
+ kPublic,
+ };
- SymbolTypes types = SymbolTypes::kAll;
+ SymbolTypes types = SymbolTypes::kAll;
- /**
- * A list of JavaDoc annotations to add to the comments of all generated classes.
- */
- std::vector<std::string> javadocAnnotations;
+ /**
+ * A list of JavaDoc annotations to add to the comments of all generated
+ * classes.
+ */
+ std::vector<std::string> javadocAnnotations;
};
/*
* Generates the R.java file for a resource table.
*/
class JavaClassGenerator {
-public:
- JavaClassGenerator(IAaptContext* context, ResourceTable* table,
- const JavaClassGeneratorOptions& options);
+ public:
+ JavaClassGenerator(IAaptContext* context, ResourceTable* table,
+ const JavaClassGeneratorOptions& options);
- /*
- * Writes the R.java file to `out`. Only symbols belonging to `package` are written.
- * All symbols technically belong to a single package, but linked libraries will
- * have their names mangled, denoting that they came from a different package.
- * We need to generate these symbols in a separate file.
- * Returns true on success.
- */
- bool generate(const StringPiece& packageNameToGenerate, std::ostream* out);
+ /*
+ * Writes the R.java file to `out`. Only symbols belonging to `package` are
+ * written.
+ * All symbols technically belong to a single package, but linked libraries
+ * will
+ * have their names mangled, denoting that they came from a different package.
+ * We need to generate these symbols in a separate file.
+ * Returns true on success.
+ */
+ bool generate(const StringPiece& packageNameToGenerate, std::ostream* out);
- bool generate(const StringPiece& packageNameToGenerate,
- const StringPiece& outputPackageName,
- std::ostream* out);
+ bool generate(const StringPiece& packageNameToGenerate,
+ const StringPiece& outputPackageName, std::ostream* out);
- const std::string& getError() const;
+ const std::string& getError() const;
-private:
- bool addMembersToTypeClass(const StringPiece& packageNameToGenerate,
- const ResourceTablePackage* package,
- const ResourceTableType* type,
- ClassDefinition* outTypeClassDef);
+ private:
+ bool addMembersToTypeClass(const StringPiece& packageNameToGenerate,
+ const ResourceTablePackage* package,
+ const ResourceTableType* type,
+ ClassDefinition* outTypeClassDef);
- void addMembersToStyleableClass(const StringPiece& packageNameToGenerate,
- const std::string& entryName,
- const Styleable* styleable,
- ClassDefinition* outStyleableClassDef);
+ void addMembersToStyleableClass(const StringPiece& packageNameToGenerate,
+ const std::string& entryName,
+ const Styleable* styleable,
+ ClassDefinition* outStyleableClassDef);
- bool skipSymbol(SymbolState state);
+ bool skipSymbol(SymbolState state);
- IAaptContext* mContext;
- ResourceTable* mTable;
- JavaClassGeneratorOptions mOptions;
- std::string mError;
+ IAaptContext* mContext;
+ ResourceTable* mTable;
+ JavaClassGeneratorOptions mOptions;
+ std::string mError;
};
inline const std::string& JavaClassGenerator::getError() const {
- return mError;
+ return mError;
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_JAVA_CLASS_GENERATOR_H
+#endif // AAPT_JAVA_CLASS_GENERATOR_H
diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h
index f565289..1817648 100644
--- a/tools/aapt2/java/ManifestClassGenerator.h
+++ b/tools/aapt2/java/ManifestClassGenerator.h
@@ -26,8 +26,9 @@
namespace aapt {
-std::unique_ptr<ClassDefinition> generateManifestClass(IDiagnostics* diag, xml::XmlResource* res);
+std::unique_ptr<ClassDefinition> generateManifestClass(IDiagnostics* diag,
+ xml::XmlResource* res);
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_JAVA_MANIFESTCLASSGENERATOR_H */
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index c2d2bd9..7578ec2 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -30,29 +30,31 @@
namespace proguard {
class KeepSet {
-public:
- inline void addClass(const Source& source, const std::string& className) {
- mKeepSet[className].insert(source);
- }
+ public:
+ inline void addClass(const Source& source, const std::string& className) {
+ mKeepSet[className].insert(source);
+ }
- inline void addMethod(const Source& source, const std::string& methodName) {
- mKeepMethodSet[methodName].insert(source);
- }
+ inline void addMethod(const Source& source, const std::string& methodName) {
+ mKeepMethodSet[methodName].insert(source);
+ }
-private:
- friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
+ private:
+ friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
- std::map<std::string, std::set<Source>> mKeepSet;
- std::map<std::string, std::set<Source>> mKeepMethodSet;
+ std::map<std::string, std::set<Source>> mKeepSet;
+ std::map<std::string, std::set<Source>> mKeepMethodSet;
};
-bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keepSet,
+bool collectProguardRulesForManifest(const Source& source,
+ xml::XmlResource* res, KeepSet* keepSet,
bool mainDexOnly = false);
-bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet);
+bool collectProguardRules(const Source& source, xml::XmlResource* res,
+ KeepSet* keepSet);
bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
-} // namespace proguard
-} // namespace aapt
+} // namespace proguard
+} // namespace aapt
-#endif // AAPT_PROGUARD_RULES_H
+#endif // AAPT_PROGUARD_RULES_H
diff --git a/tools/aapt2/jni/Aapt2.java b/tools/aapt2/jni/Aapt2.java
new file mode 100644
index 0000000..aed23de
--- /dev/null
+++ b/tools/aapt2/jni/Aapt2.java
@@ -0,0 +1,44 @@
+package com.android.tools.aapt2;
+
+import java.util.List;
+
+/**
+ * {@code aapt2} JNI interface. To use the {@code aapt2} native interface, the
+ * shared library must first be loaded and then a new instance of this class can
+ * be used to access the library.
+ */
+public class Aapt2 {
+
+ /**
+ * Invokes {@code aapt2} to perform resource compilation.
+ *
+ * @param arguments arguments for compilation (see {@code Compile.cpp})
+ */
+ public static void compile(List<String> arguments) {
+ nativeCompile(arguments);
+ }
+
+ /**
+ * Invokes {@code aapt2} to perform linking.
+ *
+ * @param arguments arguments for linking (see {@code Link.cpp})
+ */
+ public static void link(List<String> arguments) {
+ nativeLink(arguments);
+ }
+
+ /**
+ * JNI call.
+ *
+ * @param arguments arguments for compilation (see {@code Compile.cpp})
+ */
+ private static native void nativeCompile(List<String> arguments);
+
+ /**
+ * JNI call.
+ *
+ * @param arguments arguments for linking (see {@code Link.cpp})
+ */
+ private static native void nativeLink(List<String> arguments);
+}
+
diff --git a/tools/aapt2/jni/Makefile b/tools/aapt2/jni/Makefile
new file mode 100644
index 0000000..a9e628f
--- /dev/null
+++ b/tools/aapt2/jni/Makefile
@@ -0,0 +1,25 @@
+#
+# This Makefile will generate the JNI headers for the Aapt2 class.
+#
+
+AAPT2_PKG=com.android.tools.aapt2
+AAPT2_DIR=$(shell echo -n com/android/tools/aapt2 | tr . /)
+OUT=out
+OUT_CLASSES=$(OUT)/classes
+OUT_HEADERS=.
+
+AAPT2_JAVA=Aapt2.java
+AAPT2_CLASSES=$(OUT_CLASSES)/$(AAPT2_DIR)/Aapt2.class
+
+AAPT2_HEADERS=$(OUT_HEADERS)/Aapt2.h
+
+all: $(AAPT2_HEADERS)
+
+$(AAPT2_HEADERS): $(AAPT2_JAVA) $(AAPT2_CLASSES)
+ mkdir -p $(OUT_HEADERS)
+ $(JAVA_HOME)/bin/javah -d $(OUT_HEADERS) -cp $(OUT_CLASSES) $(AAPT2_PKG).Aapt2
+
+$(AAPT2_CLASSES): $(AAPT2_JAVA)
+ mkdir -p $(OUT_CLASSES)
+ javac -d $(OUT_CLASSES) $(AAPT2_JAVA)
+
diff --git a/tools/aapt2/jni/aapt2_jni.cpp b/tools/aapt2/jni/aapt2_jni.cpp
new file mode 100644
index 0000000..dff77b9
--- /dev/null
+++ b/tools/aapt2/jni/aapt2_jni.cpp
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+#include <algorithm>
+#include <cassert>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <ScopedUtfChars.h>
+
+#include "util/Util.h"
+
+#include "com_android_tools_aapt2_Aapt2.h"
+
+namespace aapt {
+ extern int compile(const std::vector<StringPiece>& args);
+ extern int link(const std::vector<StringPiece>& args);
+}
+
+/*
+ * Converts a java List<String> into C++ vector<ScopedUtfChars>.
+ */
+static std::vector<ScopedUtfChars> list_to_utfchars(JNIEnv *env, jobject obj) {
+ std::vector<ScopedUtfChars> converted;
+
+ // Call size() method on the list to know how many elements there are.
+ jclass list_cls = env->GetObjectClass(obj);
+ jmethodID size_method_id = env->GetMethodID(list_cls, "size", "()I");
+ assert(size_method_id != 0);
+ jint size = env->CallIntMethod(obj, size_method_id);
+ assert(size >= 0);
+
+ // Now, iterate all strings in the list
+ // (note: generic erasure means get() return an Object)
+ jmethodID get_method_id = env->GetMethodID(list_cls, "get", "()Ljava/lang/Object;");
+ for (jint i = 0; i < size; i++) {
+ // Call get(i) to get the string in the ith position.
+ jobject string_obj_uncast = env->CallObjectMethod(obj, get_method_id, i);
+ assert(string_obj_uncast != nullptr);
+ jstring string_obj = static_cast<jstring>(string_obj_uncast);
+ converted.push_back(ScopedUtfChars(env, string_obj));
+ }
+
+ return converted;
+}
+
+/*
+ * Extracts all StringPiece from the ScopedUtfChars instances.
+ *
+ * The returned pieces can only be used while the original ones have not been
+ * destroyed.
+ */
+static std::vector<aapt::StringPiece> extract_pieces(
+ const std::vector<ScopedUtfChars> &strings) {
+ std::vector<aapt::StringPiece> pieces;
+
+ std::for_each(
+ strings.begin(),
+ strings.end(),
+ [&pieces](const ScopedUtfChars &p) {
+ pieces.push_back(p.c_str());
+ });
+
+ return pieces;
+}
+
+JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2_nativeCompile(
+ JNIEnv *env,
+ jclass aapt_obj,
+ jobject arguments_obj) {
+ std::vector<ScopedUtfChars> compile_args_jni = list_to_utfchars(env, arguments_obj);
+ std::vector<aapt::StringPiece> compile_args = extract_pieces(compile_args_jni);
+ aapt::compile(compile_args);
+}
+
+JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2_nativeLink(
+ JNIEnv *env,
+ jclass aapt_obj,
+ jobject arguments_obj) {
+ std::vector<ScopedUtfChars> link_args_jni = list_to_utfchars(env, arguments_obj);
+ std::vector<aapt::StringPiece> link_args = extract_pieces(link_args_jni);
+ aapt::link(link_args);
+}
diff --git a/tools/aapt2/jni/com_android_tools_aapt2_Aapt2.h b/tools/aapt2/jni/com_android_tools_aapt2_Aapt2.h
new file mode 100644
index 0000000..443b98f
--- /dev/null
+++ b/tools/aapt2/jni/com_android_tools_aapt2_Aapt2.h
@@ -0,0 +1,29 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_android_tools_aapt2_Aapt2 */
+
+#ifndef _Included_com_android_tools_aapt2_Aapt2
+#define _Included_com_android_tools_aapt2_Aapt2
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: com_android_tools_aapt2_Aapt2
+ * Method: nativeCompile
+ * Signature: (Ljava/util/List;)V
+ */
+JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2_nativeCompile
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: com_android_tools_aapt2_Aapt2
+ * Method: nativeLink
+ * Signature: (Ljava/util/List;)V
+ */
+JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2_nativeLink
+ (JNIEnv *, jclass, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp
index 8ed27c3..5ba9819 100644
--- a/tools/aapt2/link/AutoVersioner.cpp
+++ b/tools/aapt2/link/AutoVersioner.cpp
@@ -25,117 +25,128 @@
namespace aapt {
-bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
+bool shouldGenerateVersionedResource(const ResourceEntry* entry,
+ const ConfigDescription& config,
const int sdkVersionToGenerate) {
- // We assume the caller is trying to generate a version greater than the current configuration.
- assert(sdkVersionToGenerate > config.sdkVersion);
+ // We assume the caller is trying to generate a version greater than the
+ // current configuration.
+ assert(sdkVersionToGenerate > config.sdkVersion);
- const auto endIter = entry->values.end();
- auto iter = entry->values.begin();
- for (; iter != endIter; ++iter) {
- if ((*iter)->config == config) {
- break;
- }
+ const auto endIter = entry->values.end();
+ auto iter = entry->values.begin();
+ for (; iter != endIter; ++iter) {
+ if ((*iter)->config == config) {
+ break;
}
+ }
- // The source config came from this list, so it should be here.
- assert(iter != entry->values.end());
- ++iter;
+ // The source config came from this list, so it should be here.
+ assert(iter != entry->values.end());
+ ++iter;
- // The next configuration either only varies in sdkVersion, or it is completely different
- // and therefore incompatible. If it is incompatible, we must generate the versioned resource.
+ // The next configuration either only varies in sdkVersion, or it is
+ // completely different
+ // and therefore incompatible. If it is incompatible, we must generate the
+ // versioned resource.
- // NOTE: The ordering of configurations takes sdkVersion as higher precedence than other
- // qualifiers, so we need to iterate through the entire list to be sure there
- // are no higher sdk level versions of this resource.
- ConfigDescription tempConfig(config);
- for (; iter != endIter; ++iter) {
- tempConfig.sdkVersion = (*iter)->config.sdkVersion;
- if (tempConfig == (*iter)->config) {
- // The two configs are the same, check the sdk version.
- return sdkVersionToGenerate < (*iter)->config.sdkVersion;
- }
+ // NOTE: The ordering of configurations takes sdkVersion as higher precedence
+ // than other
+ // qualifiers, so we need to iterate through the entire list to be sure there
+ // are no higher sdk level versions of this resource.
+ ConfigDescription tempConfig(config);
+ for (; iter != endIter; ++iter) {
+ tempConfig.sdkVersion = (*iter)->config.sdkVersion;
+ if (tempConfig == (*iter)->config) {
+ // The two configs are the same, check the sdk version.
+ return sdkVersionToGenerate < (*iter)->config.sdkVersion;
}
+ }
- // No match was found, so we should generate the versioned resource.
- return true;
+ // No match was found, so we should generate the versioned resource.
+ return true;
}
bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) {
- for (auto& package : table->packages) {
- for (auto& type : package->types) {
- if (type->type != ResourceType::kStyle) {
- continue;
- }
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ if (type->type != ResourceType::kStyle) {
+ continue;
+ }
- for (auto& entry : type->entries) {
- for (size_t i = 0; i < entry->values.size(); i++) {
- ResourceConfigValue* configValue = entry->values[i].get();
- if (configValue->config.sdkVersion >= SDK_LOLLIPOP_MR1) {
- // If this configuration is only used on L-MR1 then we don't need
- // to do anything since we use private attributes since that version.
- continue;
- }
+ for (auto& entry : type->entries) {
+ for (size_t i = 0; i < entry->values.size(); i++) {
+ ResourceConfigValue* configValue = entry->values[i].get();
+ if (configValue->config.sdkVersion >= SDK_LOLLIPOP_MR1) {
+ // If this configuration is only used on L-MR1 then we don't need
+ // to do anything since we use private attributes since that
+ // version.
+ continue;
+ }
- if (Style* style = valueCast<Style>(configValue->value.get())) {
- Maybe<size_t> minSdkStripped;
- std::vector<Style::Entry> stripped;
+ if (Style* style = valueCast<Style>(configValue->value.get())) {
+ Maybe<size_t> minSdkStripped;
+ std::vector<Style::Entry> stripped;
- auto iter = style->entries.begin();
- while (iter != style->entries.end()) {
- assert(iter->key.id && "IDs must be assigned and linked");
+ auto iter = style->entries.begin();
+ while (iter != style->entries.end()) {
+ assert(iter->key.id && "IDs must be assigned and linked");
- // Find the SDK level that is higher than the configuration allows.
- const size_t sdkLevel = findAttributeSdkLevel(iter->key.id.value());
- if (sdkLevel > std::max<size_t>(configValue->config.sdkVersion, 1)) {
- // Record that we are about to strip this.
- stripped.emplace_back(std::move(*iter));
+ // Find the SDK level that is higher than the configuration
+ // allows.
+ const size_t sdkLevel =
+ findAttributeSdkLevel(iter->key.id.value());
+ if (sdkLevel >
+ std::max<size_t>(configValue->config.sdkVersion, 1)) {
+ // Record that we are about to strip this.
+ stripped.emplace_back(std::move(*iter));
- // We use the smallest SDK level to generate the new style.
- if (minSdkStripped) {
- minSdkStripped = std::min(minSdkStripped.value(), sdkLevel);
- } else {
- minSdkStripped = sdkLevel;
- }
-
- // Erase this from this style.
- iter = style->entries.erase(iter);
- continue;
- }
- ++iter;
- }
-
- if (minSdkStripped && !stripped.empty()) {
- // We found attributes from a higher SDK level. Check that
- // there is no other defined resource for the version we want to
- // generate.
- if (shouldGenerateVersionedResource(entry.get(),
- configValue->config,
- minSdkStripped.value())) {
- // Let's create a new Style for this versioned resource.
- ConfigDescription newConfig(configValue->config);
- newConfig.sdkVersion = minSdkStripped.value();
-
- std::unique_ptr<Style> newStyle(style->clone(&table->stringPool));
- newStyle->setComment(style->getComment());
- newStyle->setSource(style->getSource());
-
- // Move the previously stripped attributes into this style.
- newStyle->entries.insert(newStyle->entries.end(),
- std::make_move_iterator(stripped.begin()),
- std::make_move_iterator(stripped.end()));
-
- // Insert the new Resource into the correct place.
- entry->findOrCreateValue(newConfig, {})->value =
- std::move(newStyle);
- }
- }
- }
+ // We use the smallest SDK level to generate the new style.
+ if (minSdkStripped) {
+ minSdkStripped = std::min(minSdkStripped.value(), sdkLevel);
+ } else {
+ minSdkStripped = sdkLevel;
}
+
+ // Erase this from this style.
+ iter = style->entries.erase(iter);
+ continue;
+ }
+ ++iter;
}
+
+ if (minSdkStripped && !stripped.empty()) {
+ // We found attributes from a higher SDK level. Check that
+ // there is no other defined resource for the version we want to
+ // generate.
+ if (shouldGenerateVersionedResource(entry.get(),
+ configValue->config,
+ minSdkStripped.value())) {
+ // Let's create a new Style for this versioned resource.
+ ConfigDescription newConfig(configValue->config);
+ newConfig.sdkVersion = minSdkStripped.value();
+
+ std::unique_ptr<Style> newStyle(
+ style->clone(&table->stringPool));
+ newStyle->setComment(style->getComment());
+ newStyle->setSource(style->getSource());
+
+ // Move the previously stripped attributes into this style.
+ newStyle->entries.insert(
+ newStyle->entries.end(),
+ std::make_move_iterator(stripped.begin()),
+ std::make_move_iterator(stripped.end()));
+
+ // Insert the new Resource into the correct place.
+ entry->findOrCreateValue(newConfig, {})->value =
+ std::move(newStyle);
+ }
+ }
+ }
}
+ }
}
- return true;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp
index 3a61da5..04bf9cd 100644
--- a/tools/aapt2/link/AutoVersioner_test.cpp
+++ b/tools/aapt2/link/AutoVersioner_test.cpp
@@ -21,102 +21,114 @@
namespace aapt {
TEST(AutoVersionerTest, GenerateVersionedResources) {
- const ConfigDescription defaultConfig = {};
- const ConfigDescription landConfig = test::parseConfigOrDie("land");
- const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land");
+ const ConfigDescription defaultConfig = {};
+ const ConfigDescription landConfig = test::parseConfigOrDie("land");
+ const ConfigDescription sw600dpLandConfig =
+ test::parseConfigOrDie("sw600dp-land");
- ResourceEntry entry("foo");
- entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
- entry.values.push_back(util::make_unique<ResourceConfigValue>(landConfig, ""));
- entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpLandConfig, ""));
+ ResourceEntry entry("foo");
+ entry.values.push_back(
+ util::make_unique<ResourceConfigValue>(defaultConfig, ""));
+ entry.values.push_back(
+ util::make_unique<ResourceConfigValue>(landConfig, ""));
+ entry.values.push_back(
+ util::make_unique<ResourceConfigValue>(sw600dpLandConfig, ""));
- EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
- EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17));
+ EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
+ EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17));
}
TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) {
- const ConfigDescription defaultConfig = {};
- const ConfigDescription sw600dpV13Config = test::parseConfigOrDie("sw600dp-v13");
- const ConfigDescription v21Config = test::parseConfigOrDie("v21");
+ const ConfigDescription defaultConfig = {};
+ const ConfigDescription sw600dpV13Config =
+ test::parseConfigOrDie("sw600dp-v13");
+ const ConfigDescription v21Config = test::parseConfigOrDie("v21");
- ResourceEntry entry("foo");
- entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
- entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpV13Config, ""));
- entry.values.push_back(util::make_unique<ResourceConfigValue>(v21Config, ""));
+ ResourceEntry entry("foo");
+ entry.values.push_back(
+ util::make_unique<ResourceConfigValue>(defaultConfig, ""));
+ entry.values.push_back(
+ util::make_unique<ResourceConfigValue>(sw600dpV13Config, ""));
+ entry.values.push_back(util::make_unique<ResourceConfigValue>(v21Config, ""));
- EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
- EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22));
+ EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
+ EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22));
}
TEST(AutoVersionerTest, VersionStylesForTable) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("app", 0x7f)
- .addValue("app:style/Foo", test::parseConfigOrDie("v4"), ResourceId(0x7f020000),
- test::StyleBuilder()
- .addItem("android:attr/onClick", ResourceId(0x0101026f),
- util::make_unique<Id>())
- .addItem("android:attr/paddingStart", ResourceId(0x010103b3),
- util::make_unique<Id>())
- .addItem("android:attr/requiresSmallestWidthDp",
- ResourceId(0x01010364), util::make_unique<Id>())
- .addItem("android:attr/colorAccent", ResourceId(0x01010435),
- util::make_unique<Id>())
- .build())
- .addValue("app:style/Foo", test::parseConfigOrDie("v21"), ResourceId(0x7f020000),
- test::StyleBuilder()
- .addItem("android:attr/paddingEnd", ResourceId(0x010103b4),
- util::make_unique<Id>())
- .build())
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .setPackageId("app", 0x7f)
+ .addValue(
+ "app:style/Foo", test::parseConfigOrDie("v4"),
+ ResourceId(0x7f020000),
+ test::StyleBuilder()
+ .addItem("android:attr/onClick", ResourceId(0x0101026f),
+ util::make_unique<Id>())
+ .addItem("android:attr/paddingStart", ResourceId(0x010103b3),
+ util::make_unique<Id>())
+ .addItem("android:attr/requiresSmallestWidthDp",
+ ResourceId(0x01010364), util::make_unique<Id>())
+ .addItem("android:attr/colorAccent", ResourceId(0x01010435),
+ util::make_unique<Id>())
+ .build())
+ .addValue(
+ "app:style/Foo", test::parseConfigOrDie("v21"),
+ ResourceId(0x7f020000),
+ test::StyleBuilder()
+ .addItem("android:attr/paddingEnd", ResourceId(0x010103b4),
+ util::make_unique<Id>())
+ .build())
+ .build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .setCompilationPackage("app")
- .setPackageId(0x7f)
- .build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .setCompilationPackage("app")
+ .setPackageId(0x7f)
+ .build();
- AutoVersioner versioner;
- ASSERT_TRUE(versioner.consume(context.get(), table.get()));
+ AutoVersioner versioner;
+ ASSERT_TRUE(versioner.consume(context.get(), table.get()));
- Style* style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
- test::parseConfigOrDie("v4"));
- ASSERT_NE(style, nullptr);
- ASSERT_EQ(style->entries.size(), 1u);
- AAPT_ASSERT_TRUE(style->entries.front().key.name);
- EXPECT_EQ(style->entries.front().key.name.value(),
- test::parseNameOrDie("android:attr/onClick"));
+ Style* style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
+ test::parseConfigOrDie("v4"));
+ ASSERT_NE(style, nullptr);
+ ASSERT_EQ(style->entries.size(), 1u);
+ AAPT_ASSERT_TRUE(style->entries.front().key.name);
+ EXPECT_EQ(style->entries.front().key.name.value(),
+ test::parseNameOrDie("android:attr/onClick"));
- style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
- test::parseConfigOrDie("v13"));
- ASSERT_NE(style, nullptr);
- ASSERT_EQ(style->entries.size(), 2u);
- AAPT_ASSERT_TRUE(style->entries[0].key.name);
- EXPECT_EQ(style->entries[0].key.name.value(),
- test::parseNameOrDie("android:attr/onClick"));
- AAPT_ASSERT_TRUE(style->entries[1].key.name);
- EXPECT_EQ(style->entries[1].key.name.value(),
- test::parseNameOrDie("android:attr/requiresSmallestWidthDp"));
+ style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
+ test::parseConfigOrDie("v13"));
+ ASSERT_NE(style, nullptr);
+ ASSERT_EQ(style->entries.size(), 2u);
+ AAPT_ASSERT_TRUE(style->entries[0].key.name);
+ EXPECT_EQ(style->entries[0].key.name.value(),
+ test::parseNameOrDie("android:attr/onClick"));
+ AAPT_ASSERT_TRUE(style->entries[1].key.name);
+ EXPECT_EQ(style->entries[1].key.name.value(),
+ test::parseNameOrDie("android:attr/requiresSmallestWidthDp"));
- style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
- test::parseConfigOrDie("v17"));
- ASSERT_NE(style, nullptr);
- ASSERT_EQ(style->entries.size(), 3u);
- AAPT_ASSERT_TRUE(style->entries[0].key.name);
- EXPECT_EQ(style->entries[0].key.name.value(),
- test::parseNameOrDie("android:attr/onClick"));
- AAPT_ASSERT_TRUE(style->entries[1].key.name);
- EXPECT_EQ(style->entries[1].key.name.value(),
- test::parseNameOrDie("android:attr/requiresSmallestWidthDp"));
- AAPT_ASSERT_TRUE(style->entries[2].key.name);
- EXPECT_EQ(style->entries[2].key.name.value(),
- test::parseNameOrDie("android:attr/paddingStart"));
+ style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
+ test::parseConfigOrDie("v17"));
+ ASSERT_NE(style, nullptr);
+ ASSERT_EQ(style->entries.size(), 3u);
+ AAPT_ASSERT_TRUE(style->entries[0].key.name);
+ EXPECT_EQ(style->entries[0].key.name.value(),
+ test::parseNameOrDie("android:attr/onClick"));
+ AAPT_ASSERT_TRUE(style->entries[1].key.name);
+ EXPECT_EQ(style->entries[1].key.name.value(),
+ test::parseNameOrDie("android:attr/requiresSmallestWidthDp"));
+ AAPT_ASSERT_TRUE(style->entries[2].key.name);
+ EXPECT_EQ(style->entries[2].key.name.value(),
+ test::parseNameOrDie("android:attr/paddingStart"));
- style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
- test::parseConfigOrDie("v21"));
- ASSERT_NE(style, nullptr);
- ASSERT_EQ(style->entries.size(), 1u);
- AAPT_ASSERT_TRUE(style->entries.front().key.name);
- EXPECT_EQ(style->entries.front().key.name.value(),
- test::parseNameOrDie("android:attr/paddingEnd"));
+ style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
+ test::parseConfigOrDie("v21"));
+ ASSERT_NE(style, nullptr);
+ ASSERT_EQ(style->entries.size(), 1u);
+ AAPT_ASSERT_TRUE(style->entries.front().key.name);
+ EXPECT_EQ(style->entries.front().key.name.value(),
+ test::parseNameOrDie("android:attr/paddingEnd"));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index b6b4b473..6dd34e3 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -31,9 +31,9 @@
#include "java/ManifestClassGenerator.h"
#include "java/ProguardRules.h"
#include "link/Linkers.h"
+#include "link/ManifestFixer.h"
#include "link/ProductFilter.h"
#include "link/ReferenceLinker.h"
-#include "link/ManifestFixer.h"
#include "link/TableMerger.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
@@ -47,9 +47,9 @@
#include <android-base/file.h>
#include <google/protobuf/io/coded_stream.h>
+#include <sys/stat.h>
#include <fstream>
#include <queue>
-#include <sys/stat.h>
#include <unordered_map>
#include <vector>
@@ -58,1908 +58,2054 @@
namespace aapt {
struct LinkOptions {
- std::string outputPath;
- std::string manifestPath;
- std::vector<std::string> includePaths;
- std::vector<std::string> overlayFiles;
+ std::string outputPath;
+ std::string manifestPath;
+ std::vector<std::string> includePaths;
+ std::vector<std::string> overlayFiles;
- // Java/Proguard options.
- Maybe<std::string> generateJavaClassPath;
- Maybe<std::string> customJavaPackage;
- std::set<std::string> extraJavaPackages;
- Maybe<std::string> generateProguardRulesPath;
- Maybe<std::string> generateMainDexProguardRulesPath;
+ // Java/Proguard options.
+ Maybe<std::string> generateJavaClassPath;
+ Maybe<std::string> customJavaPackage;
+ std::set<std::string> extraJavaPackages;
+ Maybe<std::string> generateProguardRulesPath;
+ Maybe<std::string> generateMainDexProguardRulesPath;
- bool noAutoVersion = false;
- bool noVersionVectors = false;
- bool noResourceDeduping = false;
- bool staticLib = false;
- bool noStaticLibPackages = false;
- bool generateNonFinalIds = false;
- std::vector<std::string> javadocAnnotations;
- bool outputToDirectory = false;
- bool noXmlNamespaces = false;
- bool autoAddOverlay = false;
- bool doNotCompressAnything = false;
- std::unordered_set<std::string> extensionsToNotCompress;
- Maybe<std::string> privateSymbols;
- ManifestFixerOptions manifestFixerOptions;
- std::unordered_set<std::string> products;
+ bool noAutoVersion = false;
+ bool noVersionVectors = false;
+ bool noResourceDeduping = false;
+ bool staticLib = false;
+ bool noStaticLibPackages = false;
+ bool generateNonFinalIds = false;
+ std::vector<std::string> javadocAnnotations;
+ bool outputToDirectory = false;
+ bool noXmlNamespaces = false;
+ bool autoAddOverlay = false;
+ bool doNotCompressAnything = false;
+ std::unordered_set<std::string> extensionsToNotCompress;
+ Maybe<std::string> privateSymbols;
+ ManifestFixerOptions manifestFixerOptions;
+ std::unordered_set<std::string> products;
- // Split APK options.
- TableSplitterOptions tableSplitterOptions;
- std::vector<SplitConstraints> splitConstraints;
- std::vector<std::string> splitPaths;
+ // Split APK options.
+ TableSplitterOptions tableSplitterOptions;
+ std::vector<SplitConstraints> splitConstraints;
+ std::vector<std::string> splitPaths;
- // Stable ID options.
- std::unordered_map<ResourceName, ResourceId> stableIdMap;
- Maybe<std::string> resourceIdMapPath;
+ // Stable ID options.
+ std::unordered_map<ResourceName, ResourceId> stableIdMap;
+ Maybe<std::string> resourceIdMapPath;
};
class LinkContext : public IAaptContext {
-public:
- LinkContext() : mNameMangler({}) {
- }
+ public:
+ LinkContext() : mNameMangler({}) {}
- IDiagnostics* getDiagnostics() override {
- return &mDiagnostics;
- }
+ IDiagnostics* getDiagnostics() override { return &mDiagnostics; }
- NameMangler* getNameMangler() override {
- return &mNameMangler;
- }
+ NameMangler* getNameMangler() override { return &mNameMangler; }
- void setNameManglerPolicy(const NameManglerPolicy& policy) {
- mNameMangler = NameMangler(policy);
- }
+ void setNameManglerPolicy(const NameManglerPolicy& policy) {
+ mNameMangler = NameMangler(policy);
+ }
- const std::string& getCompilationPackage() override {
- return mCompilationPackage;
- }
+ const std::string& getCompilationPackage() override {
+ return mCompilationPackage;
+ }
- void setCompilationPackage(const StringPiece& packageName) {
- mCompilationPackage = packageName.toString();
- }
+ void setCompilationPackage(const StringPiece& packageName) {
+ mCompilationPackage = packageName.toString();
+ }
- uint8_t getPackageId() override {
- return mPackageId;
- }
+ uint8_t getPackageId() override { return mPackageId; }
- void setPackageId(uint8_t id) {
- mPackageId = id;
- }
+ void setPackageId(uint8_t id) { mPackageId = id; }
- SymbolTable* getExternalSymbols() override {
- return &mSymbols;
- }
+ SymbolTable* getExternalSymbols() override { return &mSymbols; }
- bool verbose() override {
- return mVerbose;
- }
+ bool verbose() override { return mVerbose; }
- void setVerbose(bool val) {
- mVerbose = val;
- }
+ void setVerbose(bool val) { mVerbose = val; }
- int getMinSdkVersion() override {
- return mMinSdkVersion;
- }
+ int getMinSdkVersion() override { return mMinSdkVersion; }
- void setMinSdkVersion(int minSdk) {
- mMinSdkVersion = minSdk;
- }
+ void setMinSdkVersion(int minSdk) { mMinSdkVersion = minSdk; }
-private:
- StdErrDiagnostics mDiagnostics;
- NameMangler mNameMangler;
- std::string mCompilationPackage;
- uint8_t mPackageId = 0x0;
- SymbolTable mSymbols;
- bool mVerbose = false;
- int mMinSdkVersion = 0;
+ private:
+ StdErrDiagnostics mDiagnostics;
+ NameMangler mNameMangler;
+ std::string mCompilationPackage;
+ uint8_t mPackageId = 0x0;
+ SymbolTable mSymbols;
+ bool mVerbose = false;
+ int mMinSdkVersion = 0;
};
static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
- uint32_t compressionFlags,
- IArchiveWriter* writer, IAaptContext* context) {
- std::unique_ptr<io::IData> data = file->openAsData();
- if (!data) {
- context->getDiagnostics()->error(DiagMessage(file->getSource())
- << "failed to open file");
- return false;
- }
-
- const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
- const size_t bufferSize = data->size();
-
- if (context->verbose()) {
- context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
- }
-
- if (writer->startEntry(outPath, compressionFlags)) {
- if (writer->writeEntry(buffer, bufferSize)) {
- if (writer->finishEntry()) {
- return true;
- }
- }
- }
-
- context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
+ uint32_t compressionFlags, IArchiveWriter* writer,
+ IAaptContext* context) {
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ context->getDiagnostics()->error(DiagMessage(file->getSource())
+ << "failed to open file");
return false;
+ }
+
+ const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
+ const size_t bufferSize = data->size();
+
+ if (context->verbose()) {
+ context->getDiagnostics()->note(DiagMessage() << "writing " << outPath
+ << " to archive");
+ }
+
+ if (writer->startEntry(outPath, compressionFlags)) {
+ if (writer->writeEntry(buffer, bufferSize)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
+ }
+
+ context->getDiagnostics()->error(DiagMessage() << "failed to write file "
+ << outPath);
+ return false;
}
-static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
- bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
- BigBuffer buffer(1024);
- XmlFlattenerOptions options = {};
- options.keepRawValues = keepRawValues;
- options.maxSdkLevel = maxSdkLevel;
- XmlFlattener flattener(&buffer, options);
- if (!flattener.consume(context, xmlRes)) {
- return false;
- }
-
- if (context->verbose()) {
- DiagMessage msg;
- msg << "writing " << path << " to archive";
- if (maxSdkLevel) {
- msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues;
- }
- context->getDiagnostics()->note(msg);
- }
-
- if (writer->startEntry(path, ArchiveEntry::kCompress)) {
- if (writer->writeEntry(buffer)) {
- if (writer->finishEntry()) {
- return true;
- }
- }
- }
- context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
+static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path,
+ Maybe<size_t> maxSdkLevel, bool keepRawValues,
+ IArchiveWriter* writer, IAaptContext* context) {
+ BigBuffer buffer(1024);
+ XmlFlattenerOptions options = {};
+ options.keepRawValues = keepRawValues;
+ options.maxSdkLevel = maxSdkLevel;
+ XmlFlattener flattener(&buffer, options);
+ if (!flattener.consume(context, xmlRes)) {
return false;
+ }
+
+ if (context->verbose()) {
+ DiagMessage msg;
+ msg << "writing " << path << " to archive";
+ if (maxSdkLevel) {
+ msg << " maxSdkLevel=" << maxSdkLevel.value()
+ << " keepRawValues=" << keepRawValues;
+ }
+ context->getDiagnostics()->note(msg);
+ }
+
+ if (writer->startEntry(path, ArchiveEntry::kCompress)) {
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
+ }
+ context->getDiagnostics()->error(DiagMessage() << "failed to write " << path
+ << " to archive");
+ return false;
}
static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
- const void* data, size_t len,
+ const void* data,
+ size_t len,
IDiagnostics* diag) {
- pb::ResourceTable pbTable;
- if (!pbTable.ParseFromArray(data, len)) {
- diag->error(DiagMessage(source) << "invalid compiled table");
- return {};
- }
+ pb::ResourceTable pbTable;
+ if (!pbTable.ParseFromArray(data, len)) {
+ diag->error(DiagMessage(source) << "invalid compiled table");
+ return {};
+ }
- std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
- if (!table) {
- return {};
- }
- return table;
+ std::unique_ptr<ResourceTable> table =
+ deserializeTableFromPb(pbTable, source, diag);
+ if (!table) {
+ return {};
+ }
+ return table;
}
/**
* Inflates an XML file from the source path.
*/
-static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
- std::ifstream fin(path, std::ifstream::binary);
- if (!fin) {
- diag->error(DiagMessage(path) << strerror(errno));
- return {};
- }
- return xml::inflate(&fin, diag, Source(path));
+static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path,
+ IDiagnostics* diag) {
+ std::ifstream fin(path, std::ifstream::binary);
+ if (!fin) {
+ diag->error(DiagMessage(path) << strerror(errno));
+ return {};
+ }
+ return xml::inflate(&fin, diag, Source(path));
}
struct ResourceFileFlattenerOptions {
- bool noAutoVersion = false;
- bool noVersionVectors = false;
- bool noXmlNamespaces = false;
- bool keepRawValues = false;
- bool doNotCompressAnything = false;
- bool updateProguardSpec = false;
- std::unordered_set<std::string> extensionsToNotCompress;
+ bool noAutoVersion = false;
+ bool noVersionVectors = false;
+ bool noXmlNamespaces = false;
+ bool keepRawValues = false;
+ bool doNotCompressAnything = false;
+ bool updateProguardSpec = false;
+ std::unordered_set<std::string> extensionsToNotCompress;
};
class ResourceFileFlattener {
-public:
- ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
- IAaptContext* context, proguard::KeepSet* keepSet) :
- mOptions(options), mContext(context), mKeepSet(keepSet) {
- }
+ public:
+ ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
+ IAaptContext* context, proguard::KeepSet* keepSet)
+ : mOptions(options), mContext(context), mKeepSet(keepSet) {}
- bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
+ bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
-private:
- struct FileOperation {
- ConfigDescription config;
+ private:
+ struct FileOperation {
+ ConfigDescription config;
- // The entry this file came from.
- const ResourceEntry* entry;
+ // The entry this file came from.
+ const ResourceEntry* entry;
- // The file to copy as-is.
- io::IFile* fileToCopy;
+ // The file to copy as-is.
+ io::IFile* fileToCopy;
- // The XML to process and flatten.
- std::unique_ptr<xml::XmlResource> xmlToFlatten;
+ // The XML to process and flatten.
+ std::unique_ptr<xml::XmlResource> xmlToFlatten;
- // The destination to write this file to.
- std::string dstPath;
- bool skipVersion = false;
- };
+ // The destination to write this file to.
+ std::string dstPath;
+ bool skipVersion = false;
+ };
- uint32_t getCompressionFlags(const StringPiece& str);
+ uint32_t getCompressionFlags(const StringPiece& str);
- bool linkAndVersionXmlFile(ResourceTable* table, FileOperation* fileOp,
- std::queue<FileOperation>* outFileOpQueue);
+ bool linkAndVersionXmlFile(ResourceTable* table, FileOperation* fileOp,
+ std::queue<FileOperation>* outFileOpQueue);
- ResourceFileFlattenerOptions mOptions;
- IAaptContext* mContext;
- proguard::KeepSet* mKeepSet;
+ ResourceFileFlattenerOptions mOptions;
+ IAaptContext* mContext;
+ proguard::KeepSet* mKeepSet;
};
uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
- if (mOptions.doNotCompressAnything) {
- return 0;
- }
+ if (mOptions.doNotCompressAnything) {
+ return 0;
+ }
- for (const std::string& extension : mOptions.extensionsToNotCompress) {
- if (util::stringEndsWith(str, extension)) {
- return 0;
- }
+ for (const std::string& extension : mOptions.extensionsToNotCompress) {
+ if (util::stringEndsWith(str, extension)) {
+ return 0;
}
- return ArchiveEntry::kCompress;
+ }
+ return ArchiveEntry::kCompress;
}
-bool ResourceFileFlattener::linkAndVersionXmlFile(ResourceTable* table,
- FileOperation* fileOp,
- std::queue<FileOperation>* outFileOpQueue) {
- xml::XmlResource* doc = fileOp->xmlToFlatten.get();
- const Source& src = doc->file.source;
+bool ResourceFileFlattener::linkAndVersionXmlFile(
+ ResourceTable* table, FileOperation* fileOp,
+ std::queue<FileOperation>* outFileOpQueue) {
+ xml::XmlResource* doc = fileOp->xmlToFlatten.get();
+ const Source& src = doc->file.source;
- if (mContext->verbose()) {
- mContext->getDiagnostics()->note(DiagMessage() << "linking " << src.path);
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(DiagMessage() << "linking " << src.path);
+ }
+
+ XmlReferenceLinker xmlLinker;
+ if (!xmlLinker.consume(mContext, doc)) {
+ return false;
+ }
+
+ if (mOptions.updateProguardSpec &&
+ !proguard::collectProguardRules(src, doc, mKeepSet)) {
+ return false;
+ }
+
+ if (mOptions.noXmlNamespaces) {
+ XmlNamespaceRemover namespaceRemover;
+ if (!namespaceRemover.consume(mContext, doc)) {
+ return false;
}
+ }
- XmlReferenceLinker xmlLinker;
- if (!xmlLinker.consume(mContext, doc)) {
- return false;
- }
-
- if (mOptions.updateProguardSpec && !proguard::collectProguardRules(src, doc, mKeepSet)) {
- return false;
- }
-
- if (mOptions.noXmlNamespaces) {
- XmlNamespaceRemover namespaceRemover;
- if (!namespaceRemover.consume(mContext, doc)) {
- return false;
+ if (!mOptions.noAutoVersion) {
+ if (mOptions.noVersionVectors) {
+ // Skip this if it is a vector or animated-vector.
+ xml::Element* el = xml::findRootElement(doc);
+ if (el && el->namespaceUri.empty()) {
+ if (el->name == "vector" || el->name == "animated-vector") {
+ // We are NOT going to version this file.
+ fileOp->skipVersion = true;
+ return true;
}
+ }
}
- if (!mOptions.noAutoVersion) {
- if (mOptions.noVersionVectors) {
- // Skip this if it is a vector or animated-vector.
- xml::Element* el = xml::findRootElement(doc);
- if (el && el->namespaceUri.empty()) {
- if (el->name == "vector" || el->name == "animated-vector") {
- // We are NOT going to version this file.
- fileOp->skipVersion = true;
- return true;
- }
- }
+ const ConfigDescription& config = fileOp->config;
+
+ // Find the first SDK level used that is higher than this defined config and
+ // not superseded by a lower or equal SDK level resource.
+ const int minSdkVersion = mContext->getMinSdkVersion();
+ for (int sdkLevel : xmlLinker.getSdkLevels()) {
+ if (sdkLevel > minSdkVersion && sdkLevel > config.sdkVersion) {
+ if (!shouldGenerateVersionedResource(fileOp->entry, config, sdkLevel)) {
+ // If we shouldn't generate a versioned resource, stop checking.
+ break;
}
- const ConfigDescription& config = fileOp->config;
+ ResourceFile versionedFileDesc = doc->file;
+ versionedFileDesc.config.sdkVersion = (uint16_t)sdkLevel;
- // Find the first SDK level used that is higher than this defined config and
- // not superseded by a lower or equal SDK level resource.
- const int minSdkVersion = mContext->getMinSdkVersion();
- for (int sdkLevel : xmlLinker.getSdkLevels()) {
- if (sdkLevel > minSdkVersion && sdkLevel > config.sdkVersion) {
- if (!shouldGenerateVersionedResource(fileOp->entry, config, sdkLevel)) {
- // If we shouldn't generate a versioned resource, stop checking.
- break;
- }
+ FileOperation newFileOp;
+ newFileOp.xmlToFlatten = util::make_unique<xml::XmlResource>(
+ versionedFileDesc, doc->root->clone());
+ newFileOp.config = versionedFileDesc.config;
+ newFileOp.entry = fileOp->entry;
+ newFileOp.dstPath = ResourceUtils::buildResourceFileName(
+ versionedFileDesc, mContext->getNameMangler());
- ResourceFile versionedFileDesc = doc->file;
- versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel;
-
- FileOperation newFileOp;
- newFileOp.xmlToFlatten = util::make_unique<xml::XmlResource>(
- versionedFileDesc, doc->root->clone());
- newFileOp.config = versionedFileDesc.config;
- newFileOp.entry = fileOp->entry;
- newFileOp.dstPath = ResourceUtils::buildResourceFileName(
- versionedFileDesc, mContext->getNameMangler());
-
- if (mContext->verbose()) {
- mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
- << "auto-versioning resource from config '"
- << config
- << "' -> '"
- << versionedFileDesc.config << "'");
- }
-
- bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
- versionedFileDesc.config,
- versionedFileDesc.source,
- newFileOp.dstPath,
- nullptr,
- mContext->getDiagnostics());
- if (!added) {
- return false;
- }
-
- outFileOpQueue->push(std::move(newFileOp));
- break;
- }
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(
+ DiagMessage(versionedFileDesc.source)
+ << "auto-versioning resource from config '" << config << "' -> '"
+ << versionedFileDesc.config << "'");
}
+
+ bool added = table->addFileReferenceAllowMangled(
+ versionedFileDesc.name, versionedFileDesc.config,
+ versionedFileDesc.source, newFileOp.dstPath, nullptr,
+ mContext->getDiagnostics());
+ if (!added) {
+ return false;
+ }
+
+ outFileOpQueue->push(std::move(newFileOp));
+ break;
+ }
}
- return true;
+ }
+ return true;
}
/**
- * Do not insert or remove any resources while executing in this function. It will
+ * Do not insert or remove any resources while executing in this function. It
+ * will
* corrupt the iteration order.
*/
-bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
- bool error = false;
- std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> configSortedFiles;
+bool ResourceFileFlattener::flatten(ResourceTable* table,
+ IArchiveWriter* archiveWriter) {
+ bool error = false;
+ std::map<std::pair<ConfigDescription, StringPiece>, FileOperation>
+ configSortedFiles;
- for (auto& pkg : table->packages) {
- for (auto& type : pkg->types) {
- // Sort by config and name, so that we get better locality in the zip file.
- configSortedFiles.clear();
- std::queue<FileOperation> fileOperations;
+ for (auto& pkg : table->packages) {
+ for (auto& type : pkg->types) {
+ // Sort by config and name, so that we get better locality in the zip
+ // file.
+ configSortedFiles.clear();
+ std::queue<FileOperation> fileOperations;
- // Populate the queue with all files in the ResourceTable.
- for (auto& entry : type->entries) {
- for (auto& configValue : entry->values) {
- FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
- if (!fileRef) {
- continue;
- }
-
- io::IFile* file = fileRef->file;
- if (!file) {
- mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
- << "file not found");
- return false;
- }
-
- FileOperation fileOp;
- fileOp.entry = entry.get();
- fileOp.dstPath = *fileRef->path;
- fileOp.config = configValue->config;
-
- const StringPiece srcPath = file->getSource().path;
- if (type->type != ResourceType::kRaw &&
- (util::stringEndsWith(srcPath, ".xml.flat") ||
- util::stringEndsWith(srcPath, ".xml"))) {
- std::unique_ptr<io::IData> data = file->openAsData();
- if (!data) {
- mContext->getDiagnostics()->error(DiagMessage(file->getSource())
- << "failed to open file");
- return false;
- }
-
- fileOp.xmlToFlatten = xml::inflate(data->data(), data->size(),
- mContext->getDiagnostics(),
- file->getSource());
-
- if (!fileOp.xmlToFlatten) {
- return false;
- }
-
- fileOp.xmlToFlatten->file.config = configValue->config;
- fileOp.xmlToFlatten->file.source = fileRef->getSource();
- fileOp.xmlToFlatten->file.name =
- ResourceName(pkg->name, type->type, entry->name);
-
- // Enqueue the XML files to be processed.
- fileOperations.push(std::move(fileOp));
- } else {
- fileOp.fileToCopy = file;
-
- // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else
- // we end up copying the string in the std::make_pair() method, then
- // creating a StringPiece from the copy, which would cause us to end up
- // referencing garbage in the map.
- const StringPiece entryName(entry->name);
- configSortedFiles[std::make_pair(configValue->config, entryName)] =
- std::move(fileOp);
- }
- }
- }
-
- // Now process the XML queue
- for (; !fileOperations.empty(); fileOperations.pop()) {
- FileOperation& fileOp = fileOperations.front();
-
- if (!linkAndVersionXmlFile(table, &fileOp, &fileOperations)) {
- error = true;
- continue;
- }
-
- // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else
- // we end up copying the string in the std::make_pair() method, then creating
- // a StringPiece from the copy, which would cause us to end up referencing
- // garbage in the map.
- const StringPiece entryName(fileOp.entry->name);
- configSortedFiles[std::make_pair(fileOp.config, entryName)] = std::move(fileOp);
- }
-
- if (error) {
- return false;
- }
-
- // Now flatten the sorted values.
- for (auto& mapEntry : configSortedFiles) {
- const ConfigDescription& config = mapEntry.first.first;
- const FileOperation& fileOp = mapEntry.second;
-
- if (fileOp.xmlToFlatten) {
- Maybe<size_t> maxSdkLevel;
- if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
- maxSdkLevel = std::max<size_t>(std::max<size_t>(config.sdkVersion, 1u),
- mContext->getMinSdkVersion());
- }
-
- bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
- mOptions.keepRawValues,
- archiveWriter, mContext);
- if (!result) {
- error = true;
- }
- } else {
- bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
- getCompressionFlags(fileOp.dstPath),
- archiveWriter, mContext);
- if (!result) {
- error = true;
- }
- }
- }
- }
- }
- return !error;
-}
-
-static bool writeStableIdMapToPath(IDiagnostics* diag,
- const std::unordered_map<ResourceName, ResourceId>& idMap,
- const std::string& idMapPath) {
- std::ofstream fout(idMapPath, std::ofstream::binary);
- if (!fout) {
- diag->error(DiagMessage(idMapPath) << strerror(errno));
- return false;
- }
-
- for (const auto& entry : idMap) {
- const ResourceName& name = entry.first;
- const ResourceId& id = entry.second;
- fout << name << " = " << id << "\n";
- }
-
- if (!fout) {
- diag->error(DiagMessage(idMapPath) << "failed writing to file: " << strerror(errno));
- return false;
- }
-
- return true;
-}
-
-static bool loadStableIdMap(IDiagnostics* diag, const std::string& path,
- std::unordered_map<ResourceName, ResourceId>* outIdMap) {
- std::string content;
- if (!android::base::ReadFileToString(path, &content)) {
- diag->error(DiagMessage(path) << "failed reading stable ID file");
- return false;
- }
-
- outIdMap->clear();
- size_t lineNo = 0;
- for (StringPiece line : util::tokenize(content, '\n')) {
- lineNo++;
- line = util::trimWhitespace(line);
- if (line.empty()) {
+ // Populate the queue with all files in the ResourceTable.
+ for (auto& entry : type->entries) {
+ for (auto& configValue : entry->values) {
+ FileReference* fileRef =
+ valueCast<FileReference>(configValue->value.get());
+ if (!fileRef) {
continue;
- }
+ }
- auto iter = std::find(line.begin(), line.end(), '=');
- if (iter == line.end()) {
- diag->error(DiagMessage(Source(path, lineNo)) << "missing '='");
+ io::IFile* file = fileRef->file;
+ if (!file) {
+ mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
+ << "file not found");
return false;
+ }
+
+ FileOperation fileOp;
+ fileOp.entry = entry.get();
+ fileOp.dstPath = *fileRef->path;
+ fileOp.config = configValue->config;
+
+ const StringPiece srcPath = file->getSource().path;
+ if (type->type != ResourceType::kRaw &&
+ (util::stringEndsWith(srcPath, ".xml.flat") ||
+ util::stringEndsWith(srcPath, ".xml"))) {
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext->getDiagnostics()->error(DiagMessage(file->getSource())
+ << "failed to open file");
+ return false;
+ }
+
+ fileOp.xmlToFlatten =
+ xml::inflate(data->data(), data->size(),
+ mContext->getDiagnostics(), file->getSource());
+
+ if (!fileOp.xmlToFlatten) {
+ return false;
+ }
+
+ fileOp.xmlToFlatten->file.config = configValue->config;
+ fileOp.xmlToFlatten->file.source = fileRef->getSource();
+ fileOp.xmlToFlatten->file.name =
+ ResourceName(pkg->name, type->type, entry->name);
+
+ // Enqueue the XML files to be processed.
+ fileOperations.push(std::move(fileOp));
+ } else {
+ fileOp.fileToCopy = file;
+
+ // NOTE(adamlesinski): Explicitly construct a StringPiece here, or
+ // else
+ // we end up copying the string in the std::make_pair() method, then
+ // creating a StringPiece from the copy, which would cause us to end
+ // up
+ // referencing garbage in the map.
+ const StringPiece entryName(entry->name);
+ configSortedFiles[std::make_pair(configValue->config, entryName)] =
+ std::move(fileOp);
+ }
+ }
+ }
+
+ // Now process the XML queue
+ for (; !fileOperations.empty(); fileOperations.pop()) {
+ FileOperation& fileOp = fileOperations.front();
+
+ if (!linkAndVersionXmlFile(table, &fileOp, &fileOperations)) {
+ error = true;
+ continue;
}
- ResourceNameRef name;
- StringPiece resNameStr = util::trimWhitespace(
- line.substr(0, std::distance(line.begin(), iter)));
- if (!ResourceUtils::parseResourceName(resNameStr, &name)) {
- diag->error(DiagMessage(Source(path, lineNo))
- << "invalid resource name '" << resNameStr << "'");
- return false;
+ // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else
+ // we end up copying the string in the std::make_pair() method, then
+ // creating
+ // a StringPiece from the copy, which would cause us to end up
+ // referencing
+ // garbage in the map.
+ const StringPiece entryName(fileOp.entry->name);
+ configSortedFiles[std::make_pair(fileOp.config, entryName)] =
+ std::move(fileOp);
+ }
+
+ if (error) {
+ return false;
+ }
+
+ // Now flatten the sorted values.
+ for (auto& mapEntry : configSortedFiles) {
+ const ConfigDescription& config = mapEntry.first.first;
+ const FileOperation& fileOp = mapEntry.second;
+
+ if (fileOp.xmlToFlatten) {
+ Maybe<size_t> maxSdkLevel;
+ if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
+ maxSdkLevel =
+ std::max<size_t>(std::max<size_t>(config.sdkVersion, 1u),
+ mContext->getMinSdkVersion());
+ }
+
+ bool result =
+ flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
+ mOptions.keepRawValues, archiveWriter, mContext);
+ if (!result) {
+ error = true;
+ }
+ } else {
+ bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
+ getCompressionFlags(fileOp.dstPath),
+ archiveWriter, mContext);
+ if (!result) {
+ error = true;
+ }
}
-
- const size_t resIdStartIdx = std::distance(line.begin(), iter) + 1;
- const size_t resIdStrLen = line.size() - resIdStartIdx;
- StringPiece resIdStr = util::trimWhitespace(line.substr(resIdStartIdx, resIdStrLen));
-
- Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(resIdStr);
- if (!maybeId) {
- diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource ID '"
- << resIdStr << "'");
- return false;
- }
-
- (*outIdMap)[name.toResourceName()] = maybeId.value();
+ }
}
- return true;
+ }
+ return !error;
+}
+
+static bool writeStableIdMapToPath(
+ IDiagnostics* diag,
+ const std::unordered_map<ResourceName, ResourceId>& idMap,
+ const std::string& idMapPath) {
+ std::ofstream fout(idMapPath, std::ofstream::binary);
+ if (!fout) {
+ diag->error(DiagMessage(idMapPath) << strerror(errno));
+ return false;
+ }
+
+ for (const auto& entry : idMap) {
+ const ResourceName& name = entry.first;
+ const ResourceId& id = entry.second;
+ fout << name << " = " << id << "\n";
+ }
+
+ if (!fout) {
+ diag->error(DiagMessage(idMapPath) << "failed writing to file: "
+ << strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+static bool loadStableIdMap(
+ IDiagnostics* diag, const std::string& path,
+ std::unordered_map<ResourceName, ResourceId>* outIdMap) {
+ std::string content;
+ if (!android::base::ReadFileToString(path, &content)) {
+ diag->error(DiagMessage(path) << "failed reading stable ID file");
+ return false;
+ }
+
+ outIdMap->clear();
+ size_t lineNo = 0;
+ for (StringPiece line : util::tokenize(content, '\n')) {
+ lineNo++;
+ line = util::trimWhitespace(line);
+ if (line.empty()) {
+ continue;
+ }
+
+ auto iter = std::find(line.begin(), line.end(), '=');
+ if (iter == line.end()) {
+ diag->error(DiagMessage(Source(path, lineNo)) << "missing '='");
+ return false;
+ }
+
+ ResourceNameRef name;
+ StringPiece resNameStr =
+ util::trimWhitespace(line.substr(0, std::distance(line.begin(), iter)));
+ if (!ResourceUtils::parseResourceName(resNameStr, &name)) {
+ diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource name '"
+ << resNameStr << "'");
+ return false;
+ }
+
+ const size_t resIdStartIdx = std::distance(line.begin(), iter) + 1;
+ const size_t resIdStrLen = line.size() - resIdStartIdx;
+ StringPiece resIdStr =
+ util::trimWhitespace(line.substr(resIdStartIdx, resIdStrLen));
+
+ Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(resIdStr);
+ if (!maybeId) {
+ diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource ID '"
+ << resIdStr << "'");
+ return false;
+ }
+
+ (*outIdMap)[name.toResourceName()] = maybeId.value();
+ }
+ return true;
}
static bool parseSplitParameter(const StringPiece& arg, IDiagnostics* diag,
- std::string* outPath, SplitConstraints* outSplit) {
- std::vector<std::string> parts = util::split(arg, ':');
- if (parts.size() != 2) {
- diag->error(DiagMessage() << "invalid split parameter '" << arg << "'");
- diag->note(DiagMessage() << "should be --split path/to/output.apk:<config>[,<config>...]");
- return false;
+ std::string* outPath,
+ SplitConstraints* outSplit) {
+ std::vector<std::string> parts = util::split(arg, ':');
+ if (parts.size() != 2) {
+ diag->error(DiagMessage() << "invalid split parameter '" << arg << "'");
+ diag->note(
+ DiagMessage()
+ << "should be --split path/to/output.apk:<config>[,<config>...]");
+ return false;
+ }
+ *outPath = parts[0];
+ std::vector<ConfigDescription> configs;
+ for (const StringPiece& configStr : util::tokenize(parts[1], ',')) {
+ configs.push_back({});
+ if (!ConfigDescription::parse(configStr, &configs.back())) {
+ diag->error(DiagMessage() << "invalid config '" << configStr
+ << "' in split parameter '" << arg << "'");
+ return false;
}
- *outPath = parts[0];
- std::vector<ConfigDescription> configs;
- for (const StringPiece& configStr : util::tokenize(parts[1], ',')) {
- configs.push_back({});
- if (!ConfigDescription::parse(configStr, &configs.back())) {
- diag->error(DiagMessage() << "invalid config '" << configStr
- << "' in split parameter '" << arg << "'");
- return false;
- }
- }
- outSplit->configs.insert(configs.begin(), configs.end());
- return true;
+ }
+ outSplit->configs.insert(configs.begin(), configs.end());
+ return true;
}
class LinkCommand {
-public:
- LinkCommand(LinkContext* context, const LinkOptions& options) :
- mOptions(options), mContext(context), mFinalTable(),
- mFileCollection(util::make_unique<io::FileCollection>()) {
- }
+ public:
+ LinkCommand(LinkContext* context, const LinkOptions& options)
+ : mOptions(options),
+ mContext(context),
+ mFinalTable(),
+ mFileCollection(util::make_unique<io::FileCollection>()) {}
- /**
- * Creates a SymbolTable that loads symbols from the various APKs and caches the
- * results for faster lookup.
- */
- bool loadSymbolsFromIncludePaths() {
- std::unique_ptr<AssetManagerSymbolSource> assetSource =
- util::make_unique<AssetManagerSymbolSource>();
- for (const std::string& path : mOptions.includePaths) {
- if (mContext->verbose()) {
- mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
- }
+ /**
+ * Creates a SymbolTable that loads symbols from the various APKs and caches
+ * the
+ * results for faster lookup.
+ */
+ bool loadSymbolsFromIncludePaths() {
+ std::unique_ptr<AssetManagerSymbolSource> assetSource =
+ util::make_unique<AssetManagerSymbolSource>();
+ for (const std::string& path : mOptions.includePaths) {
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(DiagMessage(path)
+ << "loading include path");
+ }
- // First try to load the file as a static lib.
- std::string errorStr;
- std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr);
- if (staticInclude) {
- if (!mOptions.staticLib) {
- // Can't include static libraries when not building a static library.
- mContext->getDiagnostics()->error(
- DiagMessage(path) << "can't include static library when building app");
- return false;
- }
-
- // If we are using --no-static-lib-packages, we need to rename the package of this
- // table to our compilation package.
- if (mOptions.noStaticLibPackages) {
- if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) {
- pkg->name = mContext->getCompilationPackage();
- }
- }
-
- mContext->getExternalSymbols()->appendSource(
- util::make_unique<ResourceTableSymbolSource>(staticInclude.get()));
-
- mStaticTableIncludes.push_back(std::move(staticInclude));
-
- } else if (!errorStr.empty()) {
- // We had an error with reading, so fail.
- mContext->getDiagnostics()->error(DiagMessage(path) << errorStr);
- return false;
- }
-
- if (!assetSource->addAssetPath(path)) {
- mContext->getDiagnostics()->error(
- DiagMessage(path) << "failed to load include path");
- return false;
- }
- }
-
- mContext->getExternalSymbols()->appendSource(std::move(assetSource));
- return true;
- }
-
- Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes, IDiagnostics* diag) {
- // Make sure the first element is <manifest> with package attribute.
- if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
- AppInfo appInfo;
-
- if (!manifestEl->namespaceUri.empty() || manifestEl->name != "manifest") {
- diag->error(DiagMessage(xmlRes->file.source) << "root tag must be <manifest>");
- return {};
- }
-
- xml::Attribute* packageAttr = manifestEl->findAttribute({}, "package");
- if (!packageAttr) {
- diag->error(DiagMessage(xmlRes->file.source)
- << "<manifest> must have a 'package' attribute");
- return {};
- }
-
- appInfo.package = packageAttr->value;
-
- if (xml::Attribute* versionCodeAttr =
- manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode")) {
- Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(versionCodeAttr->value);
- if (!maybeCode) {
- diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber))
- << "invalid android:versionCode '"
- << versionCodeAttr->value << "'");
- return {};
- }
- appInfo.versionCode = maybeCode.value();
- }
-
- if (xml::Attribute* revisionCodeAttr =
- manifestEl->findAttribute(xml::kSchemaAndroid, "revisionCode")) {
- Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(revisionCodeAttr->value);
- if (!maybeCode) {
- diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber))
- << "invalid android:revisionCode '"
- << revisionCodeAttr->value << "'");
- return {};
- }
- appInfo.revisionCode = maybeCode.value();
- }
-
- if (xml::Element* usesSdkEl = manifestEl->findChild({}, "uses-sdk")) {
- if (xml::Attribute* minSdk =
- usesSdkEl->findAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
- appInfo.minSdkVersion = minSdk->value;
- }
- }
-
- return appInfo;
- }
- return {};
- }
-
- /**
- * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
- * Postcondition: ResourceTable has only one package left. All others are stripped, or there
- * is an error and false is returned.
- */
- bool verifyNoExternalPackages() {
- auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
- return mContext->getCompilationPackage() != pkg->name ||
- !pkg->id ||
- pkg->id.value() != mContext->getPackageId();
- };
-
- bool error = false;
- for (const auto& package : mFinalTable.packages) {
- if (isExtPackageFunc(package)) {
- // We have a package that is not related to the one we're building!
- for (const auto& type : package->types) {
- for (const auto& entry : type->entries) {
- ResourceNameRef resName(package->name, type->type, entry->name);
-
- for (const auto& configValue : entry->values) {
- // Special case the occurrence of an ID that is being generated for the
- // 'android' package. This is due to legacy reasons.
- if (valueCast<Id>(configValue->value.get()) &&
- package->name == "android") {
- mContext->getDiagnostics()->warn(
- DiagMessage(configValue->value->getSource())
- << "generated id '" << resName
- << "' for external package '" << package->name
- << "'");
- } else {
- mContext->getDiagnostics()->error(
- DiagMessage(configValue->value->getSource())
- << "defined resource '" << resName
- << "' for external package '" << package->name
- << "'");
- error = true;
- }
- }
- }
- }
- }
- }
-
- auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
- isExtPackageFunc);
- mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
- return !error;
- }
-
- /**
- * Returns true if no IDs have been set, false otherwise.
- */
- bool verifyNoIdsSet() {
- for (const auto& package : mFinalTable.packages) {
- for (const auto& type : package->types) {
- if (type->id) {
- mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type
- << " has ID " << std::hex
- << (int) type->id.value()
- << std::dec << " assigned");
- return false;
- }
-
- for (const auto& entry : type->entries) {
- if (entry->id) {
- ResourceNameRef resName(package->name, type->type, entry->name);
- mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName
- << " has ID " << std::hex
- << (int) entry->id.value()
- << std::dec << " assigned");
- return false;
- }
- }
- }
- }
- return true;
- }
-
- std::unique_ptr<IArchiveWriter> makeArchiveWriter(const StringPiece& out) {
- if (mOptions.outputToDirectory) {
- return createDirectoryArchiveWriter(mContext->getDiagnostics(), out);
- } else {
- return createZipFileArchiveWriter(mContext->getDiagnostics(), out);
- }
- }
-
- bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
- BigBuffer buffer(1024);
- TableFlattener flattener(&buffer);
- if (!flattener.consume(mContext, table)) {
- return false;
- }
-
- if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
- if (writer->writeEntry(buffer)) {
- if (writer->finishEntry()) {
- return true;
- }
- }
- }
-
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed to write resources.arsc to archive");
- return false;
- }
-
- bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
- // Create the file/zip entry.
- if (!writer->startEntry("resources.arsc.flat", 0)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed to open");
- return false;
- }
-
- // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
- {
- // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
- // interface.
- CopyingOutputStreamAdaptor adaptor(writer);
-
- std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
- if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
- return false;
- }
- }
-
- if (!writer->finishEntry()) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry");
- return false;
- }
- return true;
- }
-
- bool writeJavaFile(ResourceTable* table, const StringPiece& packageNameToGenerate,
- const StringPiece& outPackage, const JavaClassGeneratorOptions& javaOptions) {
- if (!mOptions.generateJavaClassPath) {
- return true;
- }
-
- std::string outPath = mOptions.generateJavaClassPath.value();
- file::appendPath(&outPath, file::packageToPath(outPackage));
- if (!file::mkdirs(outPath)) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed to create directory '" << outPath << "'");
- return false;
- }
-
- file::appendPath(&outPath, "R.java");
-
- std::ofstream fout(outPath, std::ofstream::binary);
- if (!fout) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
- return false;
- }
-
- JavaClassGenerator generator(mContext, table, javaOptions);
- if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
- mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
- return false;
- }
-
- if (!fout) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
- }
- return true;
- }
-
- bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
- if (!mOptions.generateJavaClassPath) {
- return true;
- }
-
- std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass(
- mContext->getDiagnostics(), manifestXml);
-
- if (!manifestClass) {
- // Something bad happened, but we already logged it, so exit.
- return false;
- }
-
- if (manifestClass->empty()) {
- // Empty Manifest class, no need to generate it.
- return true;
- }
-
- // Add any JavaDoc annotations to the generated class.
- for (const std::string& annotation : mOptions.javadocAnnotations) {
- std::string properAnnotation = "@";
- properAnnotation += annotation;
- manifestClass->getCommentBuilder()->appendComment(properAnnotation);
- }
-
- const std::string& packageUtf8 = mContext->getCompilationPackage();
-
- std::string outPath = mOptions.generateJavaClassPath.value();
- file::appendPath(&outPath, file::packageToPath(packageUtf8));
-
- if (!file::mkdirs(outPath)) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed to create directory '" << outPath << "'");
- return false;
- }
-
- file::appendPath(&outPath, "Manifest.java");
-
- std::ofstream fout(outPath, std::ofstream::binary);
- if (!fout) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
- return false;
- }
-
- if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
- return false;
- }
- return true;
- }
-
- bool writeProguardFile(const Maybe<std::string>& out, const proguard::KeepSet& keepSet) {
- if (!out) {
- return true;
- }
-
- const std::string& outPath = out.value();
- std::ofstream fout(outPath, std::ofstream::binary);
- if (!fout) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno));
- return false;
- }
-
- proguard::writeKeepSet(&fout, keepSet);
- if (!fout) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
- return false;
- }
- return true;
- }
-
- std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
- std::string* outError) {
- std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
- input, outError);
- if (!collection) {
- return {};
- }
- return loadTablePbFromCollection(collection.get());
- }
-
- std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) {
- io::IFile* file = collection->findFile("resources.arsc.flat");
- if (!file) {
- return {};
- }
-
- std::unique_ptr<io::IData> data = file->openAsData();
- return loadTableFromPb(file->getSource(), data->data(), data->size(),
- mContext->getDiagnostics());
- }
-
- bool mergeStaticLibrary(const std::string& input, bool override) {
- if (mContext->verbose()) {
- mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input);
- }
-
- std::string errorStr;
- std::unique_ptr<io::ZipFileCollection> collection =
- io::ZipFileCollection::create(input, &errorStr);
- if (!collection) {
- mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
- return false;
- }
-
- std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get());
- if (!table) {
- mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library");
- return false;
- }
-
- ResourceTablePackage* pkg = table->findPackageById(0x7f);
- if (!pkg) {
- mContext->getDiagnostics()->error(DiagMessage(input)
- << "static library has no package");
- return false;
- }
-
- bool result;
- if (mOptions.noStaticLibPackages) {
- // Merge all resources as if they were in the compilation package. This is the old
- // behaviour of aapt.
-
- // Add the package to the set of --extra-packages so we emit an R.java for each
- // library package.
- if (!pkg->name.empty()) {
- mOptions.extraJavaPackages.insert(pkg->name);
- }
-
- pkg->name = "";
- if (override) {
- result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get());
- } else {
- result = mTableMerger->merge(Source(input), table.get(), collection.get());
- }
-
- } else {
- // This is the proper way to merge libraries, where the package name is preserved
- // and resource names are mangled.
- result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(),
- collection.get());
- }
-
- if (!result) {
- return false;
- }
-
- // Make sure to move the collection into the set of IFileCollections.
- mCollections.push_back(std::move(collection));
- return true;
- }
-
- bool mergeResourceTable(io::IFile* file, bool override) {
- if (mContext->verbose()) {
- mContext->getDiagnostics()->note(DiagMessage() << "merging resource table "
- << file->getSource());
- }
-
- std::unique_ptr<io::IData> data = file->openAsData();
- if (!data) {
- mContext->getDiagnostics()->error(DiagMessage(file->getSource())
- << "failed to open file");
- return false;
- }
-
- std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
- data->data(), data->size(),
- mContext->getDiagnostics());
- if (!table) {
- return false;
- }
-
- bool result = false;
- if (override) {
- result = mTableMerger->mergeOverlay(file->getSource(), table.get());
- } else {
- result = mTableMerger->merge(file->getSource(), table.get());
- }
- return result;
- }
-
- bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) {
- if (mContext->verbose()) {
- mContext->getDiagnostics()->note(DiagMessage()
- << "merging '" << fileDesc->name
- << "' from compiled file "
- << file->getSource());
- }
-
- bool result = false;
- if (override) {
- result = mTableMerger->mergeFileOverlay(*fileDesc, file);
- } else {
- result = mTableMerger->mergeFile(*fileDesc, file);
- }
-
- if (!result) {
- return false;
- }
-
- // Add the exports of this file to the table.
- for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
- if (exportedSymbol.name.package.empty()) {
- exportedSymbol.name.package = mContext->getCompilationPackage();
- }
-
- ResourceNameRef resName = exportedSymbol.name;
-
- Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
- exportedSymbol.name);
- if (mangledName) {
- resName = mangledName.value();
- }
-
- std::unique_ptr<Id> id = util::make_unique<Id>();
- id->setSource(fileDesc->source.withLine(exportedSymbol.line));
- bool result = mFinalTable.addResourceAllowMangled(
- resName, ConfigDescription::defaultConfig(), std::string(), std::move(id),
- mContext->getDiagnostics());
- if (!result) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
- * If override is true, conflicting resources are allowed to override each other, in order of
- * last seen.
- *
- * An io::IFileCollection is created from the ZIP file and added to the set of
- * io::IFileCollections that are open.
- */
- bool mergeArchive(const std::string& input, bool override) {
- if (mContext->verbose()) {
- mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input);
- }
-
- std::string errorStr;
- std::unique_ptr<io::ZipFileCollection> collection =
- io::ZipFileCollection::create(input, &errorStr);
- if (!collection) {
- mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
- return false;
- }
-
- bool error = false;
- for (auto iter = collection->iterator(); iter->hasNext(); ) {
- if (!mergeFile(iter->next(), override)) {
- error = true;
- }
- }
-
- // Make sure to move the collection into the set of IFileCollections.
- mCollections.push_back(std::move(collection));
- return !error;
- }
-
- /**
- * Takes a path to load and merge into the master ResourceTable. If override is true,
- * conflicting resources are allowed to override each other, in order of last seen.
- *
- * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive
- * and the files within are merged individually.
- *
- * Otherwise the files is processed on its own.
- */
- bool mergePath(const std::string& path, bool override) {
- if (util::stringEndsWith(path, ".flata") ||
- util::stringEndsWith(path, ".jar") ||
- util::stringEndsWith(path, ".jack") ||
- util::stringEndsWith(path, ".zip")) {
- return mergeArchive(path, override);
- } else if (util::stringEndsWith(path, ".apk")) {
- return mergeStaticLibrary(path, override);
- }
-
- io::IFile* file = mFileCollection->insertFile(path);
- return mergeFile(file, override);
- }
-
- /**
- * Takes a file to load and merge into the master ResourceTable. If override is true,
- * conflicting resources are allowed to override each other, in order of last seen.
- *
- * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the
- * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file
- * and the header data is read and merged into the final ResourceTable.
- *
- * All other file types are ignored. This is because these files could be coming from a zip,
- * where we could have other files like classes.dex.
- */
- bool mergeFile(io::IFile* file, bool override) {
- const Source& src = file->getSource();
- if (util::stringEndsWith(src.path, ".arsc.flat")) {
- return mergeResourceTable(file, override);
-
- } else if (util::stringEndsWith(src.path, ".flat")){
- // Try opening the file and looking for an Export header.
- std::unique_ptr<io::IData> data = file->openAsData();
- if (!data) {
- mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
- return false;
- }
-
- CompiledFileInputStream inputStream(data->data(), data->size());
- uint32_t numFiles = 0;
- if (!inputStream.ReadLittleEndian32(&numFiles)) {
- mContext->getDiagnostics()->error(DiagMessage(src) << "failed read num files");
- return false;
- }
-
- for (uint32_t i = 0; i < numFiles; i++) {
- pb::CompiledFile compiledFile;
- if (!inputStream.ReadCompiledFile(&compiledFile)) {
- mContext->getDiagnostics()->error(DiagMessage(src)
- << "failed to read compiled file header");
- return false;
- }
-
- uint64_t offset, len;
- if (!inputStream.ReadDataMetaData(&offset, &len)) {
- mContext->getDiagnostics()->error(DiagMessage(src)
- << "failed to read data meta data");
- return false;
- }
-
- std::unique_ptr<ResourceFile> resourceFile = deserializeCompiledFileFromPb(
- compiledFile, file->getSource(), mContext->getDiagnostics());
- if (!resourceFile) {
- return false;
- }
-
- if (!mergeCompiledFile(file->createFileSegment(offset, len), resourceFile.get(),
- override)) {
- return false;
- }
- }
- return true;
- }
-
- // Ignore non .flat files. This could be classes.dex or something else that happens
- // to be in an archive.
- return true;
- }
-
- std::unique_ptr<xml::XmlResource> generateSplitManifest(const AppInfo& appInfo,
- const SplitConstraints& constraints) {
- std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>();
-
- std::unique_ptr<xml::Namespace> namespaceAndroid = util::make_unique<xml::Namespace>();
- namespaceAndroid->namespaceUri = xml::kSchemaAndroid;
- namespaceAndroid->namespacePrefix = "android";
-
- std::unique_ptr<xml::Element> manifestEl = util::make_unique<xml::Element>();
- manifestEl->name = "manifest";
- manifestEl->attributes.push_back(
- xml::Attribute{ "", "package", appInfo.package });
-
- if (appInfo.versionCode) {
- manifestEl->attributes.push_back(xml::Attribute{
- xml::kSchemaAndroid,
- "versionCode",
- std::to_string(appInfo.versionCode.value()) });
- }
-
- if (appInfo.revisionCode) {
- manifestEl->attributes.push_back(xml::Attribute{
- xml::kSchemaAndroid,
- "revisionCode", std::to_string(appInfo.revisionCode.value()) });
- }
-
- std::stringstream splitName;
- splitName << "config." << util::joiner(constraints.configs, "_");
-
- manifestEl->attributes.push_back(
- xml::Attribute{ "", "split", splitName.str() });
-
- std::unique_ptr<xml::Element> applicationEl = util::make_unique<xml::Element>();
- applicationEl->name = "application";
- applicationEl->attributes.push_back(
- xml::Attribute{ xml::kSchemaAndroid, "hasCode", "false" });
-
- manifestEl->addChild(std::move(applicationEl));
- namespaceAndroid->addChild(std::move(manifestEl));
- doc->root = std::move(namespaceAndroid);
- return doc;
- }
-
- /**
- * Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
- * to the IArchiveWriter.
- */
- bool writeApk(IArchiveWriter* writer, proguard::KeepSet* keepSet, xml::XmlResource* manifest,
- ResourceTable* table) {
- const bool keepRawValues = mOptions.staticLib;
- bool result = flattenXml(manifest, "AndroidManifest.xml", {}, keepRawValues, writer,
- mContext);
- if (!result) {
- return false;
- }
-
- ResourceFileFlattenerOptions fileFlattenerOptions;
- fileFlattenerOptions.keepRawValues = keepRawValues;
- fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
- fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
- fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
- fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
- fileFlattenerOptions.noXmlNamespaces = mOptions.noXmlNamespaces;
- fileFlattenerOptions.updateProguardSpec =
- static_cast<bool>(mOptions.generateProguardRulesPath);
-
- ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, keepSet);
-
- if (!fileFlattener.flatten(table, writer)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
- return false;
- }
-
- if (mOptions.staticLib) {
- if (!flattenTableToPb(table, writer)) {
- mContext->getDiagnostics()->error(DiagMessage()
- << "failed to write resources.arsc.flat");
- return false;
- }
- } else {
- if (!flattenTable(table, writer)) {
- mContext->getDiagnostics()->error(DiagMessage()
- << "failed to write resources.arsc");
- return false;
- }
- }
- return true;
- }
-
- int run(const std::vector<std::string>& inputFiles) {
- // Load the AndroidManifest.xml
- std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
- mContext->getDiagnostics());
- if (!manifestXml) {
- return 1;
- }
-
- // First extract the Package name without modifying it (via --rename-manifest-package).
- if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
- mContext->getDiagnostics())) {
- const AppInfo& appInfo = maybeAppInfo.value();
- mContext->setCompilationPackage(appInfo.package);
- }
-
- ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
- if (!manifestFixer.consume(mContext, manifestXml.get())) {
- return 1;
- }
-
- Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
- mContext->getDiagnostics());
- if (!maybeAppInfo) {
- return 1;
- }
-
- const AppInfo& appInfo = maybeAppInfo.value();
- if (appInfo.minSdkVersion) {
- if (Maybe<int> maybeMinSdkVersion =
- ResourceUtils::parseSdkVersion(appInfo.minSdkVersion.value())) {
- mContext->setMinSdkVersion(maybeMinSdkVersion.value());
- }
- }
-
- mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() });
- if (mContext->getCompilationPackage() == "android") {
- mContext->setPackageId(0x01);
- } else {
- mContext->setPackageId(0x7f);
- }
-
- if (!loadSymbolsFromIncludePaths()) {
- return 1;
- }
-
- TableMergerOptions tableMergerOptions;
- tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
- mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
-
- if (mContext->verbose()) {
- mContext->getDiagnostics()->note(
- DiagMessage() << "linking package '" << mContext->getCompilationPackage()
- << "' with package ID " << std::hex
- << (int) mContext->getPackageId());
- }
-
-
- for (const std::string& input : inputFiles) {
- if (!mergePath(input, false)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
- return 1;
- }
- }
-
- for (const std::string& input : mOptions.overlayFiles) {
- if (!mergePath(input, true)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
- return 1;
- }
- }
-
- if (!verifyNoExternalPackages()) {
- return 1;
- }
-
+ // First try to load the file as a static lib.
+ std::string errorStr;
+ std::unique_ptr<ResourceTable> staticInclude =
+ loadStaticLibrary(path, &errorStr);
+ if (staticInclude) {
if (!mOptions.staticLib) {
- PrivateAttributeMover mover;
- if (!mover.consume(mContext, &mFinalTable)) {
+ // Can't include static libraries when not building a static library.
+ mContext->getDiagnostics()->error(
+ DiagMessage(path)
+ << "can't include static library when building app");
+ return false;
+ }
+
+ // If we are using --no-static-lib-packages, we need to rename the
+ // package of this
+ // table to our compilation package.
+ if (mOptions.noStaticLibPackages) {
+ if (ResourceTablePackage* pkg =
+ staticInclude->findPackageById(0x7f)) {
+ pkg->name = mContext->getCompilationPackage();
+ }
+ }
+
+ mContext->getExternalSymbols()->appendSource(
+ util::make_unique<ResourceTableSymbolSource>(staticInclude.get()));
+
+ mStaticTableIncludes.push_back(std::move(staticInclude));
+
+ } else if (!errorStr.empty()) {
+ // We had an error with reading, so fail.
+ mContext->getDiagnostics()->error(DiagMessage(path) << errorStr);
+ return false;
+ }
+
+ if (!assetSource->addAssetPath(path)) {
+ mContext->getDiagnostics()->error(DiagMessage(path)
+ << "failed to load include path");
+ return false;
+ }
+ }
+
+ mContext->getExternalSymbols()->appendSource(std::move(assetSource));
+ return true;
+ }
+
+ Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes,
+ IDiagnostics* diag) {
+ // Make sure the first element is <manifest> with package attribute.
+ if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
+ AppInfo appInfo;
+
+ if (!manifestEl->namespaceUri.empty() || manifestEl->name != "manifest") {
+ diag->error(DiagMessage(xmlRes->file.source)
+ << "root tag must be <manifest>");
+ return {};
+ }
+
+ xml::Attribute* packageAttr = manifestEl->findAttribute({}, "package");
+ if (!packageAttr) {
+ diag->error(DiagMessage(xmlRes->file.source)
+ << "<manifest> must have a 'package' attribute");
+ return {};
+ }
+
+ appInfo.package = packageAttr->value;
+
+ if (xml::Attribute* versionCodeAttr =
+ manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode")) {
+ Maybe<uint32_t> maybeCode =
+ ResourceUtils::parseInt(versionCodeAttr->value);
+ if (!maybeCode) {
+ diag->error(
+ DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber))
+ << "invalid android:versionCode '" << versionCodeAttr->value
+ << "'");
+ return {};
+ }
+ appInfo.versionCode = maybeCode.value();
+ }
+
+ if (xml::Attribute* revisionCodeAttr =
+ manifestEl->findAttribute(xml::kSchemaAndroid, "revisionCode")) {
+ Maybe<uint32_t> maybeCode =
+ ResourceUtils::parseInt(revisionCodeAttr->value);
+ if (!maybeCode) {
+ diag->error(
+ DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber))
+ << "invalid android:revisionCode '" << revisionCodeAttr->value
+ << "'");
+ return {};
+ }
+ appInfo.revisionCode = maybeCode.value();
+ }
+
+ if (xml::Element* usesSdkEl = manifestEl->findChild({}, "uses-sdk")) {
+ if (xml::Attribute* minSdk = usesSdkEl->findAttribute(
+ xml::kSchemaAndroid, "minSdkVersion")) {
+ appInfo.minSdkVersion = minSdk->value;
+ }
+ }
+
+ return appInfo;
+ }
+ return {};
+ }
+
+ /**
+ * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it
+ * linked.
+ * Postcondition: ResourceTable has only one package left. All others are
+ * stripped, or there
+ * is an error and false is returned.
+ */
+ bool verifyNoExternalPackages() {
+ auto isExtPackageFunc =
+ [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
+ return mContext->getCompilationPackage() != pkg->name || !pkg->id ||
+ pkg->id.value() != mContext->getPackageId();
+ };
+
+ bool error = false;
+ for (const auto& package : mFinalTable.packages) {
+ if (isExtPackageFunc(package)) {
+ // We have a package that is not related to the one we're building!
+ for (const auto& type : package->types) {
+ for (const auto& entry : type->entries) {
+ ResourceNameRef resName(package->name, type->type, entry->name);
+
+ for (const auto& configValue : entry->values) {
+ // Special case the occurrence of an ID that is being generated
+ // for the
+ // 'android' package. This is due to legacy reasons.
+ if (valueCast<Id>(configValue->value.get()) &&
+ package->name == "android") {
+ mContext->getDiagnostics()->warn(
+ DiagMessage(configValue->value->getSource())
+ << "generated id '" << resName << "' for external package '"
+ << package->name << "'");
+ } else {
mContext->getDiagnostics()->error(
- DiagMessage() << "failed moving private attributes");
- return 1;
- }
-
- // Assign IDs if we are building a regular app.
- IdAssigner idAssigner(&mOptions.stableIdMap);
- if (!idAssigner.consume(mContext, &mFinalTable)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
- return 1;
- }
-
- // Now grab each ID and emit it as a file.
- if (mOptions.resourceIdMapPath) {
- for (auto& package : mFinalTable.packages) {
- for (auto& type : package->types) {
- for (auto& entry : type->entries) {
- ResourceName name(package->name, type->type, entry->name);
- // The IDs are guaranteed to exist.
- mOptions.stableIdMap[std::move(name)] = ResourceId(package->id.value(),
- type->id.value(),
- entry->id.value());
- }
- }
- }
-
- if (!writeStableIdMapToPath(mContext->getDiagnostics(),
- mOptions.stableIdMap,
- mOptions.resourceIdMapPath.value())) {
- return 1;
- }
- }
- } else {
- // Static libs are merged with other apps, and ID collisions are bad, so verify that
- // no IDs have been set.
- if (!verifyNoIdsSet()) {
- return 1;
- }
- }
-
- // Add the names to mangle based on our source merge earlier.
- mContext->setNameManglerPolicy(NameManglerPolicy{
- mContext->getCompilationPackage(), mTableMerger->getMergedPackages() });
-
- // Add our table to the symbol table.
- mContext->getExternalSymbols()->prependSource(
- util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
-
- ReferenceLinker linker;
- if (!linker.consume(mContext, &mFinalTable)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
- return 1;
- }
-
- if (mOptions.staticLib) {
- if (!mOptions.products.empty()) {
- mContext->getDiagnostics()->warn(
- DiagMessage() << "can't select products when building static library");
- }
- } else {
- ProductFilter productFilter(mOptions.products);
- if (!productFilter.consume(mContext, &mFinalTable)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
- return 1;
- }
- }
-
- if (!mOptions.noAutoVersion) {
- AutoVersioner versioner;
- if (!versioner.consume(mContext, &mFinalTable)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
- return 1;
- }
- }
-
- if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) {
- if (mContext->verbose()) {
- mContext->getDiagnostics()->note(
- DiagMessage() << "collapsing resource versions for minimum SDK "
- << mContext->getMinSdkVersion());
- }
-
- VersionCollapser collapser;
- if (!collapser.consume(mContext, &mFinalTable)) {
- return 1;
- }
- }
-
- if (!mOptions.noResourceDeduping) {
- ResourceDeduper deduper;
- if (!deduper.consume(mContext, &mFinalTable)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed deduping resources");
- return 1;
- }
- }
-
- proguard::KeepSet proguardKeepSet;
- proguard::KeepSet proguardMainDexKeepSet;
-
- if (mOptions.staticLib) {
- if (mOptions.tableSplitterOptions.configFilter != nullptr ||
- mOptions.tableSplitterOptions.preferredDensity) {
- mContext->getDiagnostics()->warn(
- DiagMessage() << "can't strip resources when building static library");
- }
- } else {
- // Adjust the SplitConstraints so that their SDK version is stripped if it is less
- // than or equal to the minSdk. Otherwise the resources that have had their SDK version
- // stripped due to minSdk won't ever match.
- std::vector<SplitConstraints> adjustedConstraintsList;
- adjustedConstraintsList.reserve(mOptions.splitConstraints.size());
- for (const SplitConstraints& constraints : mOptions.splitConstraints) {
- SplitConstraints adjustedConstraints;
- for (const ConfigDescription& config : constraints.configs) {
- if (config.sdkVersion <= mContext->getMinSdkVersion()) {
- adjustedConstraints.configs.insert(config.copyWithoutSdkVersion());
- } else {
- adjustedConstraints.configs.insert(config);
- }
- }
- adjustedConstraintsList.push_back(std::move(adjustedConstraints));
- }
-
- TableSplitter tableSplitter(adjustedConstraintsList, mOptions.tableSplitterOptions);
- if (!tableSplitter.verifySplitConstraints(mContext)) {
- return 1;
- }
- tableSplitter.splitTable(&mFinalTable);
-
- // Now we need to write out the Split APKs.
- auto pathIter = mOptions.splitPaths.begin();
- auto splitConstraintsIter = adjustedConstraintsList.begin();
- for (std::unique_ptr<ResourceTable>& splitTable : tableSplitter.getSplits()) {
- if (mContext->verbose()) {
- mContext->getDiagnostics()->note(
- DiagMessage(*pathIter) << "generating split with configurations '"
- << util::joiner(splitConstraintsIter->configs, ", ") << "'");
- }
-
- std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(*pathIter);
- if (!archiveWriter) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
- return 1;
- }
-
- // Generate an AndroidManifest.xml for each split.
- std::unique_ptr<xml::XmlResource> splitManifest =
- generateSplitManifest(appInfo, *splitConstraintsIter);
-
- XmlReferenceLinker linker;
- if (!linker.consume(mContext, splitManifest.get())) {
- mContext->getDiagnostics()->error(
- DiagMessage() << "failed to create Split AndroidManifest.xml");
- return 1;
- }
-
- if (!writeApk(archiveWriter.get(), &proguardKeepSet, splitManifest.get(),
- splitTable.get())) {
- return 1;
- }
-
- ++pathIter;
- ++splitConstraintsIter;
- }
- }
-
- // Start writing the base APK.
- std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(mOptions.outputPath);
- if (!archiveWriter) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
- return 1;
- }
-
- bool error = false;
- {
- // AndroidManifest.xml has no resource name, but the CallSite is built from the name
- // (aka, which package the AndroidManifest.xml is coming from).
- // So we give it a package name so it can see local resources.
- manifestXml->file.name.package = mContext->getCompilationPackage();
-
- XmlReferenceLinker manifestLinker;
- if (manifestLinker.consume(mContext, manifestXml.get())) {
- if (mOptions.generateProguardRulesPath &&
- !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
- manifestXml.get(),
- &proguardKeepSet)) {
- error = true;
- }
-
- if (mOptions.generateMainDexProguardRulesPath &&
- !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
- manifestXml.get(),
- &proguardMainDexKeepSet,
- true)) {
- error = true;
- }
-
- if (mOptions.generateJavaClassPath) {
- if (!writeManifestJavaFile(manifestXml.get())) {
- error = true;
- }
- }
-
- if (mOptions.noXmlNamespaces) {
- // PackageParser will fail if URIs are removed from AndroidManifest.xml.
- XmlNamespaceRemover namespaceRemover(true /* keepUris */);
- if (!namespaceRemover.consume(mContext, manifestXml.get())) {
- error = true;
- }
- }
- } else {
+ DiagMessage(configValue->value->getSource())
+ << "defined resource '" << resName
+ << "' for external package '" << package->name << "'");
error = true;
+ }
}
+ }
+ }
+ }
+ }
+
+ auto newEndIter =
+ std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
+ isExtPackageFunc);
+ mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
+ return !error;
+ }
+
+ /**
+ * Returns true if no IDs have been set, false otherwise.
+ */
+ bool verifyNoIdsSet() {
+ for (const auto& package : mFinalTable.packages) {
+ for (const auto& type : package->types) {
+ if (type->id) {
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "type " << type->type << " has ID " << std::hex
+ << (int)type->id.value() << std::dec
+ << " assigned");
+ return false;
}
- if (error) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
- return 1;
+ for (const auto& entry : type->entries) {
+ if (entry->id) {
+ ResourceNameRef resName(package->name, type->type, entry->name);
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "entry " << resName << " has ID " << std::hex
+ << (int)entry->id.value() << std::dec
+ << " assigned");
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ std::unique_ptr<IArchiveWriter> makeArchiveWriter(const StringPiece& out) {
+ if (mOptions.outputToDirectory) {
+ return createDirectoryArchiveWriter(mContext->getDiagnostics(), out);
+ } else {
+ return createZipFileArchiveWriter(mContext->getDiagnostics(), out);
+ }
+ }
+
+ bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(&buffer);
+ if (!flattener.consume(mContext, table)) {
+ return false;
+ }
+
+ if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
+ }
+
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed to write resources.arsc to archive");
+ return false;
+ }
+
+ bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
+ // Create the file/zip entry.
+ if (!writer->startEntry("resources.arsc.flat", 0)) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed to open");
+ return false;
+ }
+
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call
+ // writer->finishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the
+ // ZeroCopyOutputStream
+ // interface.
+ CopyingOutputStreamAdaptor adaptor(writer);
+
+ std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
+ if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
+ return false;
+ }
+ }
+
+ if (!writer->finishEntry()) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed to finish entry");
+ return false;
+ }
+ return true;
+ }
+
+ bool writeJavaFile(ResourceTable* table,
+ const StringPiece& packageNameToGenerate,
+ const StringPiece& outPackage,
+ const JavaClassGeneratorOptions& javaOptions) {
+ if (!mOptions.generateJavaClassPath) {
+ return true;
+ }
+
+ std::string outPath = mOptions.generateJavaClassPath.value();
+ file::appendPath(&outPath, file::packageToPath(outPackage));
+ if (!file::mkdirs(outPath)) {
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed to create directory '" << outPath << "'");
+ return false;
+ }
+
+ file::appendPath(&outPath, "R.java");
+
+ std::ofstream fout(outPath, std::ofstream::binary);
+ if (!fout) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed writing to '" << outPath
+ << "': " << strerror(errno));
+ return false;
+ }
+
+ JavaClassGenerator generator(mContext, table, javaOptions);
+ if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
+ mContext->getDiagnostics()->error(DiagMessage(outPath)
+ << generator.getError());
+ return false;
+ }
+
+ if (!fout) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed writing to '" << outPath
+ << "': " << strerror(errno));
+ }
+ return true;
+ }
+
+ bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
+ if (!mOptions.generateJavaClassPath) {
+ return true;
+ }
+
+ std::unique_ptr<ClassDefinition> manifestClass =
+ generateManifestClass(mContext->getDiagnostics(), manifestXml);
+
+ if (!manifestClass) {
+ // Something bad happened, but we already logged it, so exit.
+ return false;
+ }
+
+ if (manifestClass->empty()) {
+ // Empty Manifest class, no need to generate it.
+ return true;
+ }
+
+ // Add any JavaDoc annotations to the generated class.
+ for (const std::string& annotation : mOptions.javadocAnnotations) {
+ std::string properAnnotation = "@";
+ properAnnotation += annotation;
+ manifestClass->getCommentBuilder()->appendComment(properAnnotation);
+ }
+
+ const std::string& packageUtf8 = mContext->getCompilationPackage();
+
+ std::string outPath = mOptions.generateJavaClassPath.value();
+ file::appendPath(&outPath, file::packageToPath(packageUtf8));
+
+ if (!file::mkdirs(outPath)) {
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed to create directory '" << outPath << "'");
+ return false;
+ }
+
+ file::appendPath(&outPath, "Manifest.java");
+
+ std::ofstream fout(outPath, std::ofstream::binary);
+ if (!fout) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed writing to '" << outPath
+ << "': " << strerror(errno));
+ return false;
+ }
+
+ if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true,
+ &fout)) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed writing to '" << outPath
+ << "': " << strerror(errno));
+ return false;
+ }
+ return true;
+ }
+
+ bool writeProguardFile(const Maybe<std::string>& out,
+ const proguard::KeepSet& keepSet) {
+ if (!out) {
+ return true;
+ }
+
+ const std::string& outPath = out.value();
+ std::ofstream fout(outPath, std::ofstream::binary);
+ if (!fout) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed to open '" << outPath
+ << "': " << strerror(errno));
+ return false;
+ }
+
+ proguard::writeKeepSet(&fout, keepSet);
+ if (!fout) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed writing to '" << outPath
+ << "': " << strerror(errno));
+ return false;
+ }
+ return true;
+ }
+
+ std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
+ std::string* outError) {
+ std::unique_ptr<io::ZipFileCollection> collection =
+ io::ZipFileCollection::create(input, outError);
+ if (!collection) {
+ return {};
+ }
+ return loadTablePbFromCollection(collection.get());
+ }
+
+ std::unique_ptr<ResourceTable> loadTablePbFromCollection(
+ io::IFileCollection* collection) {
+ io::IFile* file = collection->findFile("resources.arsc.flat");
+ if (!file) {
+ return {};
+ }
+
+ std::unique_ptr<io::IData> data = file->openAsData();
+ return loadTableFromPb(file->getSource(), data->data(), data->size(),
+ mContext->getDiagnostics());
+ }
+
+ bool mergeStaticLibrary(const std::string& input, bool override) {
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(DiagMessage()
+ << "merging static library " << input);
+ }
+
+ std::string errorStr;
+ std::unique_ptr<io::ZipFileCollection> collection =
+ io::ZipFileCollection::create(input, &errorStr);
+ if (!collection) {
+ mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
+ return false;
+ }
+
+ std::unique_ptr<ResourceTable> table =
+ loadTablePbFromCollection(collection.get());
+ if (!table) {
+ mContext->getDiagnostics()->error(DiagMessage(input)
+ << "invalid static library");
+ return false;
+ }
+
+ ResourceTablePackage* pkg = table->findPackageById(0x7f);
+ if (!pkg) {
+ mContext->getDiagnostics()->error(DiagMessage(input)
+ << "static library has no package");
+ return false;
+ }
+
+ bool result;
+ if (mOptions.noStaticLibPackages) {
+ // Merge all resources as if they were in the compilation package. This is
+ // the old
+ // behaviour of aapt.
+
+ // Add the package to the set of --extra-packages so we emit an R.java for
+ // each
+ // library package.
+ if (!pkg->name.empty()) {
+ mOptions.extraJavaPackages.insert(pkg->name);
+ }
+
+ pkg->name = "";
+ if (override) {
+ result = mTableMerger->mergeOverlay(Source(input), table.get(),
+ collection.get());
+ } else {
+ result =
+ mTableMerger->merge(Source(input), table.get(), collection.get());
+ }
+
+ } else {
+ // This is the proper way to merge libraries, where the package name is
+ // preserved
+ // and resource names are mangled.
+ result = mTableMerger->mergeAndMangle(Source(input), pkg->name,
+ table.get(), collection.get());
+ }
+
+ if (!result) {
+ return false;
+ }
+
+ // Make sure to move the collection into the set of IFileCollections.
+ mCollections.push_back(std::move(collection));
+ return true;
+ }
+
+ bool mergeResourceTable(io::IFile* file, bool override) {
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(
+ DiagMessage() << "merging resource table " << file->getSource());
+ }
+
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext->getDiagnostics()->error(DiagMessage(file->getSource())
+ << "failed to open file");
+ return false;
+ }
+
+ std::unique_ptr<ResourceTable> table =
+ loadTableFromPb(file->getSource(), data->data(), data->size(),
+ mContext->getDiagnostics());
+ if (!table) {
+ return false;
+ }
+
+ bool result = false;
+ if (override) {
+ result = mTableMerger->mergeOverlay(file->getSource(), table.get());
+ } else {
+ result = mTableMerger->merge(file->getSource(), table.get());
+ }
+ return result;
+ }
+
+ bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc,
+ bool override) {
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(
+ DiagMessage() << "merging '" << fileDesc->name
+ << "' from compiled file " << file->getSource());
+ }
+
+ bool result = false;
+ if (override) {
+ result = mTableMerger->mergeFileOverlay(*fileDesc, file);
+ } else {
+ result = mTableMerger->mergeFile(*fileDesc, file);
+ }
+
+ if (!result) {
+ return false;
+ }
+
+ // Add the exports of this file to the table.
+ for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
+ if (exportedSymbol.name.package.empty()) {
+ exportedSymbol.name.package = mContext->getCompilationPackage();
+ }
+
+ ResourceNameRef resName = exportedSymbol.name;
+
+ Maybe<ResourceName> mangledName =
+ mContext->getNameMangler()->mangleName(exportedSymbol.name);
+ if (mangledName) {
+ resName = mangledName.value();
+ }
+
+ std::unique_ptr<Id> id = util::make_unique<Id>();
+ id->setSource(fileDesc->source.withLine(exportedSymbol.line));
+ bool result = mFinalTable.addResourceAllowMangled(
+ resName, ConfigDescription::defaultConfig(), std::string(),
+ std::move(id), mContext->getDiagnostics());
+ if (!result) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Takes a path to load as a ZIP file and merges the files within into the
+ * master ResourceTable.
+ * If override is true, conflicting resources are allowed to override each
+ * other, in order of
+ * last seen.
+ *
+ * An io::IFileCollection is created from the ZIP file and added to the set of
+ * io::IFileCollections that are open.
+ */
+ bool mergeArchive(const std::string& input, bool override) {
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(DiagMessage() << "merging archive "
+ << input);
+ }
+
+ std::string errorStr;
+ std::unique_ptr<io::ZipFileCollection> collection =
+ io::ZipFileCollection::create(input, &errorStr);
+ if (!collection) {
+ mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
+ return false;
+ }
+
+ bool error = false;
+ for (auto iter = collection->iterator(); iter->hasNext();) {
+ if (!mergeFile(iter->next(), override)) {
+ error = true;
+ }
+ }
+
+ // Make sure to move the collection into the set of IFileCollections.
+ mCollections.push_back(std::move(collection));
+ return !error;
+ }
+
+ /**
+ * Takes a path to load and merge into the master ResourceTable. If override
+ * is true,
+ * conflicting resources are allowed to override each other, in order of last
+ * seen.
+ *
+ * If the file path ends with .flata, .jar, .jack, or .zip the file is treated
+ * as ZIP archive
+ * and the files within are merged individually.
+ *
+ * Otherwise the files is processed on its own.
+ */
+ bool mergePath(const std::string& path, bool override) {
+ if (util::stringEndsWith(path, ".flata") ||
+ util::stringEndsWith(path, ".jar") ||
+ util::stringEndsWith(path, ".jack") ||
+ util::stringEndsWith(path, ".zip")) {
+ return mergeArchive(path, override);
+ } else if (util::stringEndsWith(path, ".apk")) {
+ return mergeStaticLibrary(path, override);
+ }
+
+ io::IFile* file = mFileCollection->insertFile(path);
+ return mergeFile(file, override);
+ }
+
+ /**
+ * Takes a file to load and merge into the master ResourceTable. If override
+ * is true,
+ * conflicting resources are allowed to override each other, in order of last
+ * seen.
+ *
+ * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and
+ * merged into the
+ * master ResourceTable. If the file ends with .flat, then it is treated like
+ * a compiled file
+ * and the header data is read and merged into the final ResourceTable.
+ *
+ * All other file types are ignored. This is because these files could be
+ * coming from a zip,
+ * where we could have other files like classes.dex.
+ */
+ bool mergeFile(io::IFile* file, bool override) {
+ const Source& src = file->getSource();
+ if (util::stringEndsWith(src.path, ".arsc.flat")) {
+ return mergeResourceTable(file, override);
+
+ } else if (util::stringEndsWith(src.path, ".flat")) {
+ // Try opening the file and looking for an Export header.
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
+ return false;
+ }
+
+ CompiledFileInputStream inputStream(data->data(), data->size());
+ uint32_t numFiles = 0;
+ if (!inputStream.ReadLittleEndian32(&numFiles)) {
+ mContext->getDiagnostics()->error(DiagMessage(src)
+ << "failed read num files");
+ return false;
+ }
+
+ for (uint32_t i = 0; i < numFiles; i++) {
+ pb::CompiledFile compiledFile;
+ if (!inputStream.ReadCompiledFile(&compiledFile)) {
+ mContext->getDiagnostics()->error(
+ DiagMessage(src) << "failed to read compiled file header");
+ return false;
}
- if (!writeApk(archiveWriter.get(), &proguardKeepSet, manifestXml.get(), &mFinalTable)) {
- return 1;
+ uint64_t offset, len;
+ if (!inputStream.ReadDataMetaData(&offset, &len)) {
+ mContext->getDiagnostics()->error(DiagMessage(src)
+ << "failed to read data meta data");
+ return false;
+ }
+
+ std::unique_ptr<ResourceFile> resourceFile =
+ deserializeCompiledFileFromPb(compiledFile, file->getSource(),
+ mContext->getDiagnostics());
+ if (!resourceFile) {
+ return false;
+ }
+
+ if (!mergeCompiledFile(file->createFileSegment(offset, len),
+ resourceFile.get(), override)) {
+ return false;
+ }
+ }
+ return true;
+ } else if (util::stringEndsWith(src.path, ".xml") ||
+ util::stringEndsWith(src.path, ".png")) {
+ // Since AAPT compiles these file types and appends .flat to them, seeing
+ // their raw extensions is a sign that they weren't compiled.
+ const StringPiece fileType =
+ util::stringEndsWith(src.path, ".xml") ? "XML" : "PNG";
+ mContext->getDiagnostics()->error(DiagMessage(src)
+ << "uncompiled " << fileType
+ << " file passed as argument. Must be "
+ "compiled first into .flat file.");
+ return false;
+ }
+
+ // Ignore non .flat files. This could be classes.dex or something else that
+ // happens
+ // to be in an archive.
+ return true;
+ }
+
+ std::unique_ptr<xml::XmlResource> generateSplitManifest(
+ const AppInfo& appInfo, const SplitConstraints& constraints) {
+ std::unique_ptr<xml::XmlResource> doc =
+ util::make_unique<xml::XmlResource>();
+
+ std::unique_ptr<xml::Namespace> namespaceAndroid =
+ util::make_unique<xml::Namespace>();
+ namespaceAndroid->namespaceUri = xml::kSchemaAndroid;
+ namespaceAndroid->namespacePrefix = "android";
+
+ std::unique_ptr<xml::Element> manifestEl =
+ util::make_unique<xml::Element>();
+ manifestEl->name = "manifest";
+ manifestEl->attributes.push_back(
+ xml::Attribute{"", "package", appInfo.package});
+
+ if (appInfo.versionCode) {
+ manifestEl->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "versionCode",
+ std::to_string(appInfo.versionCode.value())});
+ }
+
+ if (appInfo.revisionCode) {
+ manifestEl->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "revisionCode",
+ std::to_string(appInfo.revisionCode.value())});
+ }
+
+ std::stringstream splitName;
+ splitName << "config." << util::joiner(constraints.configs, "_");
+
+ manifestEl->attributes.push_back(
+ xml::Attribute{"", "split", splitName.str()});
+
+ std::unique_ptr<xml::Element> applicationEl =
+ util::make_unique<xml::Element>();
+ applicationEl->name = "application";
+ applicationEl->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "hasCode", "false"});
+
+ manifestEl->addChild(std::move(applicationEl));
+ namespaceAndroid->addChild(std::move(manifestEl));
+ doc->root = std::move(namespaceAndroid);
+ return doc;
+ }
+
+ /**
+ * Writes the AndroidManifest, ResourceTable, and all XML files referenced by
+ * the ResourceTable
+ * to the IArchiveWriter.
+ */
+ bool writeApk(IArchiveWriter* writer, proguard::KeepSet* keepSet,
+ xml::XmlResource* manifest, ResourceTable* table) {
+ const bool keepRawValues = mOptions.staticLib;
+ bool result = flattenXml(manifest, "AndroidManifest.xml", {}, keepRawValues,
+ writer, mContext);
+ if (!result) {
+ return false;
+ }
+
+ ResourceFileFlattenerOptions fileFlattenerOptions;
+ fileFlattenerOptions.keepRawValues = keepRawValues;
+ fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
+ fileFlattenerOptions.extensionsToNotCompress =
+ mOptions.extensionsToNotCompress;
+ fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
+ fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
+ fileFlattenerOptions.noXmlNamespaces = mOptions.noXmlNamespaces;
+ fileFlattenerOptions.updateProguardSpec =
+ static_cast<bool>(mOptions.generateProguardRulesPath);
+
+ ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext,
+ keepSet);
+
+ if (!fileFlattener.flatten(table, writer)) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed linking file resources");
+ return false;
+ }
+
+ if (mOptions.staticLib) {
+ if (!flattenTableToPb(table, writer)) {
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed to write resources.arsc.flat");
+ return false;
+ }
+ } else {
+ if (!flattenTable(table, writer)) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed to write resources.arsc");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ int run(const std::vector<std::string>& inputFiles) {
+ // Load the AndroidManifest.xml
+ std::unique_ptr<xml::XmlResource> manifestXml =
+ loadXml(mOptions.manifestPath, mContext->getDiagnostics());
+ if (!manifestXml) {
+ return 1;
+ }
+
+ // First extract the Package name without modifying it (via
+ // --rename-manifest-package).
+ if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(
+ manifestXml.get(), mContext->getDiagnostics())) {
+ const AppInfo& appInfo = maybeAppInfo.value();
+ mContext->setCompilationPackage(appInfo.package);
+ }
+
+ ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
+ if (!manifestFixer.consume(mContext, manifestXml.get())) {
+ return 1;
+ }
+
+ Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(
+ manifestXml.get(), mContext->getDiagnostics());
+ if (!maybeAppInfo) {
+ return 1;
+ }
+
+ const AppInfo& appInfo = maybeAppInfo.value();
+ if (appInfo.minSdkVersion) {
+ if (Maybe<int> maybeMinSdkVersion =
+ ResourceUtils::parseSdkVersion(appInfo.minSdkVersion.value())) {
+ mContext->setMinSdkVersion(maybeMinSdkVersion.value());
+ }
+ }
+
+ mContext->setNameManglerPolicy(
+ NameManglerPolicy{mContext->getCompilationPackage()});
+ if (mContext->getCompilationPackage() == "android") {
+ mContext->setPackageId(0x01);
+ } else {
+ mContext->setPackageId(0x7f);
+ }
+
+ if (!loadSymbolsFromIncludePaths()) {
+ return 1;
+ }
+
+ TableMergerOptions tableMergerOptions;
+ tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
+ mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable,
+ tableMergerOptions);
+
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(DiagMessage()
+ << "linking package '"
+ << mContext->getCompilationPackage()
+ << "' with package ID " << std::hex
+ << (int)mContext->getPackageId());
+ }
+
+ for (const std::string& input : inputFiles) {
+ if (!mergePath(input, false)) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed parsing input");
+ return 1;
+ }
+ }
+
+ for (const std::string& input : mOptions.overlayFiles) {
+ if (!mergePath(input, true)) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed parsing overlays");
+ return 1;
+ }
+ }
+
+ if (!verifyNoExternalPackages()) {
+ return 1;
+ }
+
+ if (!mOptions.staticLib) {
+ PrivateAttributeMover mover;
+ if (!mover.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed moving private attributes");
+ return 1;
+ }
+
+ // Assign IDs if we are building a regular app.
+ IdAssigner idAssigner(&mOptions.stableIdMap);
+ if (!idAssigner.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed assigning IDs");
+ return 1;
+ }
+
+ // Now grab each ID and emit it as a file.
+ if (mOptions.resourceIdMapPath) {
+ for (auto& package : mFinalTable.packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ ResourceName name(package->name, type->type, entry->name);
+ // The IDs are guaranteed to exist.
+ mOptions.stableIdMap[std::move(name)] = ResourceId(
+ package->id.value(), type->id.value(), entry->id.value());
+ }
+ }
+ }
+
+ if (!writeStableIdMapToPath(mContext->getDiagnostics(),
+ mOptions.stableIdMap,
+ mOptions.resourceIdMapPath.value())) {
+ return 1;
+ }
+ }
+ } else {
+ // Static libs are merged with other apps, and ID collisions are bad, so
+ // verify that
+ // no IDs have been set.
+ if (!verifyNoIdsSet()) {
+ return 1;
+ }
+ }
+
+ // Add the names to mangle based on our source merge earlier.
+ mContext->setNameManglerPolicy(NameManglerPolicy{
+ mContext->getCompilationPackage(), mTableMerger->getMergedPackages()});
+
+ // Add our table to the symbol table.
+ mContext->getExternalSymbols()->prependSource(
+ util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
+
+ ReferenceLinker linker;
+ if (!linker.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed linking references");
+ return 1;
+ }
+
+ if (mOptions.staticLib) {
+ if (!mOptions.products.empty()) {
+ mContext->getDiagnostics()
+ ->warn(DiagMessage()
+ << "can't select products when building static library");
+ }
+ } else {
+ ProductFilter productFilter(mOptions.products);
+ if (!productFilter.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed stripping products");
+ return 1;
+ }
+ }
+
+ if (!mOptions.noAutoVersion) {
+ AutoVersioner versioner;
+ if (!versioner.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed versioning styles");
+ return 1;
+ }
+ }
+
+ if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) {
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(
+ DiagMessage() << "collapsing resource versions for minimum SDK "
+ << mContext->getMinSdkVersion());
+ }
+
+ VersionCollapser collapser;
+ if (!collapser.consume(mContext, &mFinalTable)) {
+ return 1;
+ }
+ }
+
+ if (!mOptions.noResourceDeduping) {
+ ResourceDeduper deduper;
+ if (!deduper.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed deduping resources");
+ return 1;
+ }
+ }
+
+ proguard::KeepSet proguardKeepSet;
+ proguard::KeepSet proguardMainDexKeepSet;
+
+ if (mOptions.staticLib) {
+ if (mOptions.tableSplitterOptions.configFilter != nullptr ||
+ mOptions.tableSplitterOptions.preferredDensity) {
+ mContext->getDiagnostics()
+ ->warn(DiagMessage()
+ << "can't strip resources when building static library");
+ }
+ } else {
+ // Adjust the SplitConstraints so that their SDK version is stripped if it
+ // is less
+ // than or equal to the minSdk. Otherwise the resources that have had
+ // their SDK version
+ // stripped due to minSdk won't ever match.
+ std::vector<SplitConstraints> adjustedConstraintsList;
+ adjustedConstraintsList.reserve(mOptions.splitConstraints.size());
+ for (const SplitConstraints& constraints : mOptions.splitConstraints) {
+ SplitConstraints adjustedConstraints;
+ for (const ConfigDescription& config : constraints.configs) {
+ if (config.sdkVersion <= mContext->getMinSdkVersion()) {
+ adjustedConstraints.configs.insert(config.copyWithoutSdkVersion());
+ } else {
+ adjustedConstraints.configs.insert(config);
+ }
+ }
+ adjustedConstraintsList.push_back(std::move(adjustedConstraints));
+ }
+
+ TableSplitter tableSplitter(adjustedConstraintsList,
+ mOptions.tableSplitterOptions);
+ if (!tableSplitter.verifySplitConstraints(mContext)) {
+ return 1;
+ }
+ tableSplitter.splitTable(&mFinalTable);
+
+ // Now we need to write out the Split APKs.
+ auto pathIter = mOptions.splitPaths.begin();
+ auto splitConstraintsIter = adjustedConstraintsList.begin();
+ for (std::unique_ptr<ResourceTable>& splitTable :
+ tableSplitter.getSplits()) {
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(
+ DiagMessage(*pathIter)
+ << "generating split with configurations '"
+ << util::joiner(splitConstraintsIter->configs, ", ") << "'");
+ }
+
+ std::unique_ptr<IArchiveWriter> archiveWriter =
+ makeArchiveWriter(*pathIter);
+ if (!archiveWriter) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed to create archive");
+ return 1;
+ }
+
+ // Generate an AndroidManifest.xml for each split.
+ std::unique_ptr<xml::XmlResource> splitManifest =
+ generateSplitManifest(appInfo, *splitConstraintsIter);
+
+ XmlReferenceLinker linker;
+ if (!linker.consume(mContext, splitManifest.get())) {
+ mContext->getDiagnostics()->error(
+ DiagMessage() << "failed to create Split AndroidManifest.xml");
+ return 1;
+ }
+
+ if (!writeApk(archiveWriter.get(), &proguardKeepSet,
+ splitManifest.get(), splitTable.get())) {
+ return 1;
+ }
+
+ ++pathIter;
+ ++splitConstraintsIter;
+ }
+ }
+
+ // Start writing the base APK.
+ std::unique_ptr<IArchiveWriter> archiveWriter =
+ makeArchiveWriter(mOptions.outputPath);
+ if (!archiveWriter) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed to create archive");
+ return 1;
+ }
+
+ bool error = false;
+ {
+ // AndroidManifest.xml has no resource name, but the CallSite is built
+ // from the name
+ // (aka, which package the AndroidManifest.xml is coming from).
+ // So we give it a package name so it can see local resources.
+ manifestXml->file.name.package = mContext->getCompilationPackage();
+
+ XmlReferenceLinker manifestLinker;
+ if (manifestLinker.consume(mContext, manifestXml.get())) {
+ if (mOptions.generateProguardRulesPath &&
+ !proguard::collectProguardRulesForManifest(
+ Source(mOptions.manifestPath), manifestXml.get(),
+ &proguardKeepSet)) {
+ error = true;
+ }
+
+ if (mOptions.generateMainDexProguardRulesPath &&
+ !proguard::collectProguardRulesForManifest(
+ Source(mOptions.manifestPath), manifestXml.get(),
+ &proguardMainDexKeepSet, true)) {
+ error = true;
}
if (mOptions.generateJavaClassPath) {
- JavaClassGeneratorOptions options;
- options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
- options.javadocAnnotations = mOptions.javadocAnnotations;
-
- if (mOptions.staticLib || mOptions.generateNonFinalIds) {
- options.useFinal = false;
- }
-
- const StringPiece actualPackage = mContext->getCompilationPackage();
- StringPiece outputPackage = mContext->getCompilationPackage();
- if (mOptions.customJavaPackage) {
- // Override the output java package to the custom one.
- outputPackage = mOptions.customJavaPackage.value();
- }
-
- if (mOptions.privateSymbols) {
- // If we defined a private symbols package, we only emit Public symbols
- // to the original package, and private and public symbols to the private package.
-
- options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
- if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
- outputPackage, options)) {
- return 1;
- }
-
- options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
- outputPackage = mOptions.privateSymbols.value();
- }
-
- if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
- return 1;
- }
-
- for (const std::string& extraPackage : mOptions.extraJavaPackages) {
- if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
- return 1;
- }
- }
+ if (!writeManifestJavaFile(manifestXml.get())) {
+ error = true;
+ }
}
- if (!writeProguardFile(mOptions.generateProguardRulesPath, proguardKeepSet)) {
- return 1;
+ if (mOptions.noXmlNamespaces) {
+ // PackageParser will fail if URIs are removed from
+ // AndroidManifest.xml.
+ XmlNamespaceRemover namespaceRemover(true /* keepUris */);
+ if (!namespaceRemover.consume(mContext, manifestXml.get())) {
+ error = true;
+ }
}
-
- if (!writeProguardFile(mOptions.generateMainDexProguardRulesPath, proguardMainDexKeepSet)) {
- return 1;
- }
-
- if (mContext->verbose()) {
- DebugPrintTableOptions debugPrintTableOptions;
- debugPrintTableOptions.showSources = true;
- Debug::printTable(&mFinalTable, debugPrintTableOptions);
- }
- return 0;
+ } else {
+ error = true;
+ }
}
-private:
- LinkOptions mOptions;
- LinkContext* mContext;
- ResourceTable mFinalTable;
+ if (error) {
+ mContext->getDiagnostics()->error(DiagMessage()
+ << "failed processing manifest");
+ return 1;
+ }
- std::unique_ptr<TableMerger> mTableMerger;
+ if (!writeApk(archiveWriter.get(), &proguardKeepSet, manifestXml.get(),
+ &mFinalTable)) {
+ return 1;
+ }
- // A pointer to the FileCollection representing the filesystem (not archives).
- std::unique_ptr<io::FileCollection> mFileCollection;
+ if (mOptions.generateJavaClassPath) {
+ JavaClassGeneratorOptions options;
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
+ options.javadocAnnotations = mOptions.javadocAnnotations;
- // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
- std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
+ if (mOptions.staticLib || mOptions.generateNonFinalIds) {
+ options.useFinal = false;
+ }
- // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable
- // can use these.
- std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
+ const StringPiece actualPackage = mContext->getCompilationPackage();
+ StringPiece outputPackage = mContext->getCompilationPackage();
+ if (mOptions.customJavaPackage) {
+ // Override the output java package to the custom one.
+ outputPackage = mOptions.customJavaPackage.value();
+ }
+
+ if (mOptions.privateSymbols) {
+ // If we defined a private symbols package, we only emit Public symbols
+ // to the original package, and private and public symbols to the
+ // private package.
+
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
+ if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
+ outputPackage, options)) {
+ return 1;
+ }
+
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
+ outputPackage = mOptions.privateSymbols.value();
+ }
+
+ if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
+ return 1;
+ }
+
+ for (const std::string& extraPackage : mOptions.extraJavaPackages) {
+ if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage,
+ options)) {
+ return 1;
+ }
+ }
+ }
+
+ if (!writeProguardFile(mOptions.generateProguardRulesPath,
+ proguardKeepSet)) {
+ return 1;
+ }
+
+ if (!writeProguardFile(mOptions.generateMainDexProguardRulesPath,
+ proguardMainDexKeepSet)) {
+ return 1;
+ }
+
+ if (mContext->verbose()) {
+ DebugPrintTableOptions debugPrintTableOptions;
+ debugPrintTableOptions.showSources = true;
+ Debug::printTable(&mFinalTable, debugPrintTableOptions);
+ }
+ return 0;
+ }
+
+ private:
+ LinkOptions mOptions;
+ LinkContext* mContext;
+ ResourceTable mFinalTable;
+
+ std::unique_ptr<TableMerger> mTableMerger;
+
+ // A pointer to the FileCollection representing the filesystem (not archives).
+ std::unique_ptr<io::FileCollection> mFileCollection;
+
+ // A vector of IFileCollections. This is mainly here to keep ownership of the
+ // collections.
+ std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
+
+ // A vector of ResourceTables. This is here to retain ownership, so that the
+ // SymbolTable
+ // can use these.
+ std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
};
int link(const std::vector<StringPiece>& args) {
- LinkContext context;
- LinkOptions options;
- std::vector<std::string> overlayArgList;
- std::vector<std::string> extraJavaPackages;
- Maybe<std::string> configs;
- Maybe<std::string> preferredDensity;
- Maybe<std::string> productList;
- bool legacyXFlag = false;
- bool requireLocalization = false;
- bool verbose = false;
- Maybe<std::string> stableIdFilePath;
- std::vector<std::string> splitArgs;
- Flags flags = Flags()
- .requiredFlag("-o", "Output path", &options.outputPath)
- .requiredFlag("--manifest", "Path to the Android manifest to build",
- &options.manifestPath)
- .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
- .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
- "The last conflicting resource given takes precedence.",
- &overlayArgList)
- .optionalFlag("--java", "Directory in which to generate R.java",
- &options.generateJavaClassPath)
- .optionalFlag("--proguard", "Output file for generated Proguard rules",
- &options.generateProguardRulesPath)
- .optionalFlag("--proguard-main-dex",
- "Output file for generated Proguard rules for the main dex",
- &options.generateMainDexProguardRulesPath)
- .optionalSwitch("--no-auto-version",
- "Disables automatic style and layout SDK versioning",
- &options.noAutoVersion)
- .optionalSwitch("--no-version-vectors",
- "Disables automatic versioning of vector drawables. Use this only\n"
- "when building with vector drawable support library",
- &options.noVersionVectors)
- .optionalSwitch("--no-resource-deduping", "Disables automatic deduping of resources with\n"
- "identical values across compatible configurations.",
- &options.noResourceDeduping)
- .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
- &legacyXFlag)
- .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
- &requireLocalization)
- .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
- "is all configurations", &configs)
- .optionalFlag("--preferred-density",
- "Selects the closest matching density and strips out all others.",
- &preferredDensity)
- .optionalFlag("--product", "Comma separated list of product names to keep",
- &productList)
- .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
- "by -o",
- &options.outputToDirectory)
- .optionalSwitch("--no-xml-namespaces", "Removes XML namespace prefix and URI "
- "information from AndroidManifest.xml\nand XML binaries in res/*.",
- &options.noXmlNamespaces)
- .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
- "AndroidManifest.xml",
- &options.manifestFixerOptions.minSdkVersionDefault)
- .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
- "AndroidManifest.xml",
- &options.manifestFixerOptions.targetSdkVersionDefault)
- .optionalFlag("--version-code", "Version code (integer) to inject into the "
- "AndroidManifest.xml if none is present",
- &options.manifestFixerOptions.versionCodeDefault)
- .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
- "if none is present",
- &options.manifestFixerOptions.versionNameDefault)
- .optionalSwitch("--static-lib", "Generate a static Android library",
- &options.staticLib)
- .optionalSwitch("--no-static-lib-packages",
- "Merge all library resources under the app's package",
- &options.noStaticLibPackages)
- .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
- "This is implied when --static-lib is specified.",
- &options.generateNonFinalIds)
- .optionalFlag("--stable-ids", "File containing a list of name to ID mapping.",
- &stableIdFilePath)
- .optionalFlag("--emit-ids", "Emit a file at the given path with a list of name to ID\n"
- "mappings, suitable for use with --stable-ids.",
- &options.resourceIdMapPath)
- .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
- "private symbols.\n"
- "If not specified, public and private symbols will use the application's "
- "package name",
- &options.privateSymbols)
- .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
- &options.customJavaPackage)
- .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
- "package names",
- &extraJavaPackages)
- .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all "
+ LinkContext context;
+ LinkOptions options;
+ std::vector<std::string> overlayArgList;
+ std::vector<std::string> extraJavaPackages;
+ Maybe<std::string> configs;
+ Maybe<std::string> preferredDensity;
+ Maybe<std::string> productList;
+ bool legacyXFlag = false;
+ bool requireLocalization = false;
+ bool verbose = false;
+ Maybe<std::string> stableIdFilePath;
+ std::vector<std::string> splitArgs;
+ Flags flags =
+ Flags()
+ .requiredFlag("-o", "Output path", &options.outputPath)
+ .requiredFlag("--manifest", "Path to the Android manifest to build",
+ &options.manifestPath)
+ .optionalFlagList("-I", "Adds an Android APK to link against",
+ &options.includePaths)
+ .optionalFlagList(
+ "-R",
+ "Compilation unit to link, using `overlay` semantics.\n"
+ "The last conflicting resource given takes precedence.",
+ &overlayArgList)
+ .optionalFlag("--java", "Directory in which to generate R.java",
+ &options.generateJavaClassPath)
+ .optionalFlag("--proguard",
+ "Output file for generated Proguard rules",
+ &options.generateProguardRulesPath)
+ .optionalFlag(
+ "--proguard-main-dex",
+ "Output file for generated Proguard rules for the main dex",
+ &options.generateMainDexProguardRulesPath)
+ .optionalSwitch("--no-auto-version",
+ "Disables automatic style and layout SDK versioning",
+ &options.noAutoVersion)
+ .optionalSwitch("--no-version-vectors",
+ "Disables automatic versioning of vector drawables. "
+ "Use this only\n"
+ "when building with vector drawable support library",
+ &options.noVersionVectors)
+ .optionalSwitch("--no-resource-deduping",
+ "Disables automatic deduping of resources with\n"
+ "identical values across compatible configurations.",
+ &options.noResourceDeduping)
+ .optionalSwitch(
+ "-x",
+ "Legacy flag that specifies to use the package identifier 0x01",
+ &legacyXFlag)
+ .optionalSwitch("-z",
+ "Require localization of strings marked 'suggested'",
+ &requireLocalization)
+ .optionalFlag(
+ "-c",
+ "Comma separated list of configurations to include. The default\n"
+ "is all configurations",
+ &configs)
+ .optionalFlag(
+ "--preferred-density",
+ "Selects the closest matching density and strips out all others.",
+ &preferredDensity)
+ .optionalFlag("--product",
+ "Comma separated list of product names to keep",
+ &productList)
+ .optionalSwitch("--output-to-dir",
+ "Outputs the APK contents to a directory specified "
+ "by -o",
+ &options.outputToDirectory)
+ .optionalSwitch("--no-xml-namespaces",
+ "Removes XML namespace prefix and URI "
+ "information from AndroidManifest.xml\nand XML "
+ "binaries in res/*.",
+ &options.noXmlNamespaces)
+ .optionalFlag("--min-sdk-version",
+ "Default minimum SDK version to use for "
+ "AndroidManifest.xml",
+ &options.manifestFixerOptions.minSdkVersionDefault)
+ .optionalFlag("--target-sdk-version",
+ "Default target SDK version to use for "
+ "AndroidManifest.xml",
+ &options.manifestFixerOptions.targetSdkVersionDefault)
+ .optionalFlag("--version-code",
+ "Version code (integer) to inject into the "
+ "AndroidManifest.xml if none is present",
+ &options.manifestFixerOptions.versionCodeDefault)
+ .optionalFlag("--version-name",
+ "Version name to inject into the AndroidManifest.xml "
+ "if none is present",
+ &options.manifestFixerOptions.versionNameDefault)
+ .optionalSwitch("--static-lib", "Generate a static Android library",
+ &options.staticLib)
+ .optionalSwitch("--no-static-lib-packages",
+ "Merge all library resources under the app's package",
+ &options.noStaticLibPackages)
+ .optionalSwitch("--non-final-ids",
+ "Generates R.java without the final modifier.\n"
+ "This is implied when --static-lib is specified.",
+ &options.generateNonFinalIds)
+ .optionalFlag("--stable-ids",
+ "File containing a list of name to ID mapping.",
+ &stableIdFilePath)
+ .optionalFlag(
+ "--emit-ids",
+ "Emit a file at the given path with a list of name to ID\n"
+ "mappings, suitable for use with --stable-ids.",
+ &options.resourceIdMapPath)
+ .optionalFlag("--private-symbols",
+ "Package name to use when generating R.java for "
+ "private symbols.\n"
+ "If not specified, public and private symbols will use "
+ "the application's "
+ "package name",
+ &options.privateSymbols)
+ .optionalFlag("--custom-package",
+ "Custom Java package under which to generate R.java",
+ &options.customJavaPackage)
+ .optionalFlagList("--extra-packages",
+ "Generate the same R.java but with different "
+ "package names",
+ &extraJavaPackages)
+ .optionalFlagList("--add-javadoc-annotation",
+ "Adds a JavaDoc annotation to all "
"generated Java classes",
&options.javadocAnnotations)
- .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
- "overlays without <add-resource> tags",
- &options.autoAddOverlay)
- .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
- &options.manifestFixerOptions.renameManifestPackage)
- .optionalFlag("--rename-instrumentation-target-package",
- "Changes the name of the target package for instrumentation. Most useful "
- "when used\nin conjunction with --rename-manifest-package",
- &options.manifestFixerOptions.renameInstrumentationTargetPackage)
- .optionalFlagList("-0", "File extensions not to compress",
- &options.extensionsToNotCompress)
- .optionalFlagList("--split", "Split resources matching a set of configs out to a "
- "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]",
- &splitArgs)
- .optionalSwitch("-v", "Enables verbose logging",
- &verbose);
+ .optionalSwitch("--auto-add-overlay",
+ "Allows the addition of new resources in "
+ "overlays without <add-resource> tags",
+ &options.autoAddOverlay)
+ .optionalFlag("--rename-manifest-package",
+ "Renames the package in AndroidManifest.xml",
+ &options.manifestFixerOptions.renameManifestPackage)
+ .optionalFlag(
+ "--rename-instrumentation-target-package",
+ "Changes the name of the target package for instrumentation. "
+ "Most useful "
+ "when used\nin conjunction with --rename-manifest-package",
+ &options.manifestFixerOptions.renameInstrumentationTargetPackage)
+ .optionalFlagList("-0", "File extensions not to compress",
+ &options.extensionsToNotCompress)
+ .optionalFlagList(
+ "--split",
+ "Split resources matching a set of configs out to a "
+ "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]",
+ &splitArgs)
+ .optionalSwitch("-v", "Enables verbose logging", &verbose);
- if (!flags.parse("aapt2 link", args, &std::cerr)) {
+ if (!flags.parse("aapt2 link", args, &std::cerr)) {
+ return 1;
+ }
+
+ // Expand all argument-files passed into the command line. These start with
+ // '@'.
+ std::vector<std::string> argList;
+ for (const std::string& arg : flags.getArgs()) {
+ if (util::stringStartsWith(arg, "@")) {
+ const std::string path = arg.substr(1, arg.size() - 1);
+ std::string error;
+ if (!file::appendArgsFromFile(path, &argList, &error)) {
+ context.getDiagnostics()->error(DiagMessage(path) << error);
return 1;
+ }
+ } else {
+ argList.push_back(arg);
+ }
+ }
+
+ // Expand all argument-files passed to -R.
+ for (const std::string& arg : overlayArgList) {
+ if (util::stringStartsWith(arg, "@")) {
+ const std::string path = arg.substr(1, arg.size() - 1);
+ std::string error;
+ if (!file::appendArgsFromFile(path, &options.overlayFiles, &error)) {
+ context.getDiagnostics()->error(DiagMessage(path) << error);
+ return 1;
+ }
+ } else {
+ options.overlayFiles.push_back(arg);
+ }
+ }
+
+ if (verbose) {
+ context.setVerbose(verbose);
+ }
+
+ // 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());
+ }
+ }
+
+ if (productList) {
+ for (StringPiece product : util::tokenize(productList.value(), ',')) {
+ if (product != "" && product != "default") {
+ options.products.insert(product.toString());
+ }
+ }
+ }
+
+ AxisConfigFilter filter;
+ if (configs) {
+ for (const StringPiece& configStr : util::tokenize(configs.value(), ',')) {
+ ConfigDescription config;
+ LocaleValue lv;
+ if (lv.initFromFilterString(configStr)) {
+ lv.writeTo(&config);
+ } else if (!ConfigDescription::parse(configStr, &config)) {
+ context.getDiagnostics()->error(DiagMessage() << "invalid config '"
+ << configStr
+ << "' for -c option");
+ return 1;
+ }
+
+ if (config.density != 0) {
+ context.getDiagnostics()->warn(DiagMessage() << "ignoring density '"
+ << config
+ << "' for -c option");
+ } else {
+ filter.addConfig(config);
+ }
}
- // Expand all argument-files passed into the command line. These start with '@'.
- std::vector<std::string> argList;
- for (const std::string& arg : flags.getArgs()) {
- if (util::stringStartsWith(arg, "@")) {
- const std::string path = arg.substr(1, arg.size() - 1);
- std::string error;
- if (!file::appendArgsFromFile(path, &argList, &error)) {
- context.getDiagnostics()->error(DiagMessage(path) << error);
- return 1;
- }
- } else {
- argList.push_back(arg);
- }
+ options.tableSplitterOptions.configFilter = &filter;
+ }
+
+ if (preferredDensity) {
+ ConfigDescription preferredDensityConfig;
+ if (!ConfigDescription::parse(preferredDensity.value(),
+ &preferredDensityConfig)) {
+ context.getDiagnostics()->error(
+ DiagMessage() << "invalid density '" << preferredDensity.value()
+ << "' for --preferred-density option");
+ return 1;
}
- // Expand all argument-files passed to -R.
- for (const std::string& arg : overlayArgList) {
- if (util::stringStartsWith(arg, "@")) {
- const std::string path = arg.substr(1, arg.size() - 1);
- std::string error;
- if (!file::appendArgsFromFile(path, &options.overlayFiles, &error)) {
- context.getDiagnostics()->error(DiagMessage(path) << error);
- return 1;
- }
- } else {
- options.overlayFiles.push_back(arg);
- }
+ // Clear the version that can be automatically added.
+ preferredDensityConfig.sdkVersion = 0;
+
+ if (preferredDensityConfig.diff(ConfigDescription::defaultConfig()) !=
+ ConfigDescription::CONFIG_DENSITY) {
+ context.getDiagnostics()->error(
+ DiagMessage() << "invalid preferred density '"
+ << preferredDensity.value() << "'. "
+ << "Preferred density must only be a density value");
+ return 1;
}
+ options.tableSplitterOptions.preferredDensity =
+ preferredDensityConfig.density;
+ }
- if (verbose) {
- context.setVerbose(verbose);
+ if (!options.staticLib && stableIdFilePath) {
+ if (!loadStableIdMap(context.getDiagnostics(), stableIdFilePath.value(),
+ &options.stableIdMap)) {
+ return 1;
}
+ }
- // 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());
- }
+ // Populate some default no-compress extensions that are already compressed.
+ options.extensionsToNotCompress.insert(
+ {".jpg", ".jpeg", ".png", ".gif", ".wav", ".mp2", ".mp3", ".ogg",
+ ".aac", ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl",
+ ".imy", ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2",
+ ".3gpp2", ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"});
+
+ // Parse the split parameters.
+ for (const std::string& splitArg : splitArgs) {
+ options.splitPaths.push_back({});
+ options.splitConstraints.push_back({});
+ if (!parseSplitParameter(splitArg, context.getDiagnostics(),
+ &options.splitPaths.back(),
+ &options.splitConstraints.back())) {
+ return 1;
}
+ }
- if (productList) {
- for (StringPiece product : util::tokenize(productList.value(), ',')) {
- if (product != "" && product != "default") {
- options.products.insert(product.toString());
- }
- }
- }
+ // Turn off auto versioning for static-libs.
+ if (options.staticLib) {
+ options.noAutoVersion = true;
+ options.noVersionVectors = true;
+ }
- AxisConfigFilter filter;
- if (configs) {
- for (const StringPiece& configStr : util::tokenize(configs.value(), ',')) {
- ConfigDescription config;
- LocaleValue lv;
- if (lv.initFromFilterString(configStr)) {
- lv.writeTo(&config);
- } else if (!ConfigDescription::parse(configStr, &config)) {
- context.getDiagnostics()->error(
- DiagMessage() << "invalid config '" << configStr << "' for -c option");
- return 1;
- }
-
- if (config.density != 0) {
- context.getDiagnostics()->warn(
- DiagMessage() << "ignoring density '" << config << "' for -c option");
- } else {
- filter.addConfig(config);
- }
- }
-
- options.tableSplitterOptions.configFilter = &filter;
- }
-
- if (preferredDensity) {
- ConfigDescription preferredDensityConfig;
- if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
- context.getDiagnostics()->error(DiagMessage() << "invalid density '"
- << preferredDensity.value()
- << "' for --preferred-density option");
- return 1;
- }
-
- // Clear the version that can be automatically added.
- preferredDensityConfig.sdkVersion = 0;
-
- if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
- != ConfigDescription::CONFIG_DENSITY) {
- context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
- << preferredDensity.value() << "'. "
- << "Preferred density must only be a density value");
- return 1;
- }
- options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
- }
-
- if (!options.staticLib && stableIdFilePath) {
- if (!loadStableIdMap(context.getDiagnostics(), stableIdFilePath.value(),
- &options.stableIdMap)) {
- return 1;
- }
- }
-
- // Populate some default no-compress extensions that are already compressed.
- options.extensionsToNotCompress.insert({
- ".jpg", ".jpeg", ".png", ".gif",
- ".wav", ".mp2", ".mp3", ".ogg", ".aac",
- ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
- ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
- ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
- ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"});
-
- // Parse the split parameters.
- for (const std::string& splitArg : splitArgs) {
- options.splitPaths.push_back({});
- options.splitConstraints.push_back({});
- if (!parseSplitParameter(splitArg, context.getDiagnostics(), &options.splitPaths.back(),
- &options.splitConstraints.back())) {
- return 1;
- }
- }
-
- // Turn off auto versioning for static-libs.
- if (options.staticLib) {
- options.noAutoVersion = true;
- options.noVersionVectors = true;
- }
-
- LinkCommand cmd(&context, options);
- return cmd.run(argList);
+ LinkCommand cmd(&context, options);
+ return cmd.run(argList);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
index ce455da..f40c0e8 100644
--- a/tools/aapt2/link/Linkers.h
+++ b/tools/aapt2/link/Linkers.h
@@ -30,106 +30,121 @@
struct ConfigDescription;
/**
- * Defines the location in which a value exists. This determines visibility of other
+ * Defines the location in which a value exists. This determines visibility of
+ * other
* package's private symbols.
*/
struct CallSite {
- ResourceNameRef resource;
+ ResourceNameRef resource;
};
/**
- * Determines whether a versioned resource should be created. If a versioned resource already
+ * Determines whether a versioned resource should be created. If a versioned
+ * resource already
* exists, it takes precedence.
*/
-bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
+bool shouldGenerateVersionedResource(const ResourceEntry* entry,
+ const ConfigDescription& config,
const int sdkVersionToGenerate);
class AutoVersioner : public IResourceTableConsumer {
-public:
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ public:
+ bool consume(IAaptContext* context, ResourceTable* table) override;
};
class XmlAutoVersioner : public IXmlResourceConsumer {
-public:
- bool consume(IAaptContext* context, xml::XmlResource* resource) override;
+ public:
+ bool consume(IAaptContext* context, xml::XmlResource* resource) override;
};
class VersionCollapser : public IResourceTableConsumer {
-public:
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ public:
+ bool consume(IAaptContext* context, ResourceTable* table) override;
};
/**
* Removes duplicated key-value entries from dominated resources.
*/
class ResourceDeduper : public IResourceTableConsumer {
-public:
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ public:
+ bool consume(IAaptContext* context, ResourceTable* table) override;
};
/**
- * If any attribute resource values are defined as public, this consumer will move all private
- * attribute resource values to a private ^private-attr type, avoiding backwards compatibility
+ * If any attribute resource values are defined as public, this consumer will
+ * move all private
+ * attribute resource values to a private ^private-attr type, avoiding backwards
+ * compatibility
* issues with new apps running on old platforms.
*
- * The Android platform ignores resource attributes it doesn't recognize, so an app developer can
- * use new attributes in their layout XML files without worrying about versioning. This assumption
- * actually breaks on older platforms. OEMs may add private attributes that are used internally.
- * AAPT originally assigned all private attributes IDs immediately proceeding the public attributes'
+ * The Android platform ignores resource attributes it doesn't recognize, so an
+ * app developer can
+ * use new attributes in their layout XML files without worrying about
+ * versioning. This assumption
+ * actually breaks on older platforms. OEMs may add private attributes that are
+ * used internally.
+ * AAPT originally assigned all private attributes IDs immediately proceeding
+ * the public attributes'
* IDs.
*
- * This means that on a newer Android platform, an ID previously assigned to a private attribute
+ * This means that on a newer Android platform, an ID previously assigned to a
+ * private attribute
* may end up assigned to a public attribute.
*
- * App developers assume using the newer attribute is safe on older platforms because it will
- * be ignored. Instead, the platform thinks the new attribute is an older, private attribute and
- * will interpret it as such. This leads to unintended styling and exceptions thrown due to
+ * App developers assume using the newer attribute is safe on older platforms
+ * because it will
+ * be ignored. Instead, the platform thinks the new attribute is an older,
+ * private attribute and
+ * will interpret it as such. This leads to unintended styling and exceptions
+ * thrown due to
* unexpected types.
*
- * By moving the private attributes to a completely different type, this ID conflict will never
+ * By moving the private attributes to a completely different type, this ID
+ * conflict will never
* occur.
*/
struct PrivateAttributeMover : public IResourceTableConsumer {
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ bool consume(IAaptContext* context, ResourceTable* table) override;
};
/**
* Removes namespace nodes and URI information from the XmlResource.
*
- * Once an XmlResource is processed by this consumer, it is no longer able to have its attributes
- * parsed. As such, this XmlResource must have already been processed by XmlReferenceLinker.
+ * Once an XmlResource is processed by this consumer, it is no longer able to
+ * have its attributes
+ * parsed. As such, this XmlResource must have already been processed by
+ * XmlReferenceLinker.
*/
class XmlNamespaceRemover : public IXmlResourceConsumer {
-private:
- bool mKeepUris;
+ private:
+ bool mKeepUris;
-public:
- XmlNamespaceRemover(bool keepUris = false) : mKeepUris(keepUris) {
- };
+ public:
+ XmlNamespaceRemover(bool keepUris = false) : mKeepUris(keepUris){};
- bool consume(IAaptContext* context, xml::XmlResource* resource) override;
+ bool consume(IAaptContext* context, xml::XmlResource* resource) override;
};
/**
- * Resolves attributes in the XmlResource and compiles string values to resource values.
+ * Resolves attributes in the XmlResource and compiles string values to resource
+ * values.
* Once an XmlResource is processed by this linker, it is ready to be flattened.
*/
class XmlReferenceLinker : public IXmlResourceConsumer {
-private:
- std::set<int> mSdkLevelsFound;
+ private:
+ std::set<int> mSdkLevelsFound;
-public:
- bool consume(IAaptContext* context, xml::XmlResource* resource) override;
+ public:
+ bool consume(IAaptContext* context, xml::XmlResource* resource) override;
- /**
- * Once the XmlResource has been consumed, this returns the various SDK levels in which
- * framework attributes used within the XML document were defined.
- */
- inline const std::set<int>& getSdkLevels() const {
- return mSdkLevelsFound;
- }
+ /**
+ * Once the XmlResource has been consumed, this returns the various SDK levels
+ * in which
+ * framework attributes used within the XML document were defined.
+ */
+ inline const std::set<int>& getSdkLevels() const { return mSdkLevelsFound; }
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_LINKER_LINKERS_H */
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 45f5acd..3c9298b 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#include "ResourceUtils.h"
#include "link/ManifestFixer.h"
+#include "ResourceUtils.h"
#include "util/Util.h"
#include "xml/XmlActionExecutor.h"
#include "xml/XmlDom.h"
@@ -25,295 +25,310 @@
namespace aapt {
/**
- * This is how PackageManager builds class names from AndroidManifest.xml entries.
+ * This is how PackageManager builds class names from AndroidManifest.xml
+ * entries.
*/
static bool nameIsJavaClassName(xml::Element* el, xml::Attribute* attr,
SourcePathDiagnostics* diag) {
- // We allow unqualified class names (ie: .HelloActivity)
- // Since we don't know the package name, we can just make a fake one here and
- // the test will be identical as long as the real package name is valid too.
- Maybe<std::string> fullyQualifiedClassName =
- util::getFullyQualifiedClassName("a", attr->value);
+ // We allow unqualified class names (ie: .HelloActivity)
+ // Since we don't know the package name, we can just make a fake one here and
+ // the test will be identical as long as the real package name is valid too.
+ Maybe<std::string> fullyQualifiedClassName =
+ util::getFullyQualifiedClassName("a", attr->value);
- StringPiece qualifiedClassName = fullyQualifiedClassName
- ? fullyQualifiedClassName.value() : attr->value;
+ StringPiece qualifiedClassName =
+ fullyQualifiedClassName ? fullyQualifiedClassName.value() : attr->value;
- if (!util::isJavaClassName(qualifiedClassName)) {
- diag->error(DiagMessage(el->lineNumber)
- << "attribute 'android:name' in <"
- << el->name << "> tag must be a valid Java class name");
- return false;
- }
- return true;
-}
-
-static bool optionalNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) {
- if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "name")) {
- return nameIsJavaClassName(el, attr, diag);
- }
- return true;
-}
-
-static bool requiredNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) {
- if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "name")) {
- return nameIsJavaClassName(el, attr, diag);
- }
+ if (!util::isJavaClassName(qualifiedClassName)) {
diag->error(DiagMessage(el->lineNumber)
- << "<" << el->name << "> is missing attribute 'android:name'");
+ << "attribute 'android:name' in <" << el->name
+ << "> tag must be a valid Java class name");
return false;
+ }
+ return true;
+}
+
+static bool optionalNameIsJavaClassName(xml::Element* el,
+ SourcePathDiagnostics* diag) {
+ if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "name")) {
+ return nameIsJavaClassName(el, attr, diag);
+ }
+ return true;
+}
+
+static bool requiredNameIsJavaClassName(xml::Element* el,
+ SourcePathDiagnostics* diag) {
+ if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "name")) {
+ return nameIsJavaClassName(el, attr, diag);
+ }
+ diag->error(DiagMessage(el->lineNumber)
+ << "<" << el->name << "> is missing attribute 'android:name'");
+ return false;
}
static bool verifyManifest(xml::Element* el, SourcePathDiagnostics* diag) {
- xml::Attribute* attr = el->findAttribute({}, "package");
- if (!attr) {
- diag->error(DiagMessage(el->lineNumber) << "<manifest> tag is missing 'package' attribute");
- return false;
- } else if (ResourceUtils::isReference(attr->value)) {
- diag->error(DiagMessage(el->lineNumber)
- << "attribute 'package' in <manifest> tag must not be a reference");
- return false;
- } else if (!util::isJavaPackageName(attr->value)) {
- diag->error(DiagMessage(el->lineNumber)
- << "attribute 'package' in <manifest> tag is not a valid Java package name: '"
- << attr->value << "'");
- return false;
- }
- return true;
+ xml::Attribute* attr = el->findAttribute({}, "package");
+ if (!attr) {
+ diag->error(DiagMessage(el->lineNumber)
+ << "<manifest> tag is missing 'package' attribute");
+ return false;
+ } else if (ResourceUtils::isReference(attr->value)) {
+ diag->error(
+ DiagMessage(el->lineNumber)
+ << "attribute 'package' in <manifest> tag must not be a reference");
+ return false;
+ } else if (!util::isJavaPackageName(attr->value)) {
+ diag->error(DiagMessage(el->lineNumber)
+ << "attribute 'package' in <manifest> tag is not a valid Java "
+ "package name: '"
+ << attr->value << "'");
+ return false;
+ }
+ return true;
}
/**
- * The coreApp attribute in <manifest> is not a regular AAPT attribute, so type checking on it
+ * The coreApp attribute in <manifest> is not a regular AAPT attribute, so type
+ * checking on it
* is manual.
*/
static bool fixCoreAppAttribute(xml::Element* el, SourcePathDiagnostics* diag) {
- if (xml::Attribute* attr = el->findAttribute("", "coreApp")) {
- std::unique_ptr<BinaryPrimitive> result = ResourceUtils::tryParseBool(attr->value);
- if (!result) {
- diag->error(DiagMessage(el->lineNumber) << "attribute coreApp must be a boolean");
- return false;
- }
- attr->compiledValue = std::move(result);
+ if (xml::Attribute* attr = el->findAttribute("", "coreApp")) {
+ std::unique_ptr<BinaryPrimitive> result =
+ ResourceUtils::tryParseBool(attr->value);
+ if (!result) {
+ diag->error(DiagMessage(el->lineNumber)
+ << "attribute coreApp must be a boolean");
+ return false;
}
- return true;
+ attr->compiledValue = std::move(result);
+ }
+ return true;
}
-bool ManifestFixer::buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag) {
- // First verify some options.
- if (mOptions.renameManifestPackage) {
- if (!util::isJavaPackageName(mOptions.renameManifestPackage.value())) {
- diag->error(DiagMessage() << "invalid manifest package override '"
- << mOptions.renameManifestPackage.value() << "'");
- return false;
- }
+bool ManifestFixer::buildRules(xml::XmlActionExecutor* executor,
+ IDiagnostics* diag) {
+ // First verify some options.
+ if (mOptions.renameManifestPackage) {
+ if (!util::isJavaPackageName(mOptions.renameManifestPackage.value())) {
+ diag->error(DiagMessage() << "invalid manifest package override '"
+ << mOptions.renameManifestPackage.value()
+ << "'");
+ return false;
+ }
+ }
+
+ if (mOptions.renameInstrumentationTargetPackage) {
+ if (!util::isJavaPackageName(
+ mOptions.renameInstrumentationTargetPackage.value())) {
+ diag->error(DiagMessage()
+ << "invalid instrumentation target package override '"
+ << mOptions.renameInstrumentationTargetPackage.value()
+ << "'");
+ return false;
+ }
+ }
+
+ // Common intent-filter actions.
+ xml::XmlNodeAction intentFilterAction;
+ intentFilterAction["action"];
+ intentFilterAction["category"];
+ intentFilterAction["data"];
+
+ // Common meta-data actions.
+ xml::XmlNodeAction metaDataAction;
+
+ // Manifest actions.
+ xml::XmlNodeAction& manifestAction = (*executor)["manifest"];
+ manifestAction.action(verifyManifest);
+ manifestAction.action(fixCoreAppAttribute);
+ manifestAction.action([&](xml::Element* el) -> bool {
+ if (mOptions.versionNameDefault) {
+ if (el->findAttribute(xml::kSchemaAndroid, "versionName") == nullptr) {
+ el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "versionName",
+ mOptions.versionNameDefault.value()});
+ }
}
- if (mOptions.renameInstrumentationTargetPackage) {
- if (!util::isJavaPackageName(mOptions.renameInstrumentationTargetPackage.value())) {
- diag->error(DiagMessage() << "invalid instrumentation target package override '"
- << mOptions.renameInstrumentationTargetPackage.value() << "'");
- return false;
- }
+ if (mOptions.versionCodeDefault) {
+ if (el->findAttribute(xml::kSchemaAndroid, "versionCode") == nullptr) {
+ el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "versionCode",
+ mOptions.versionCodeDefault.value()});
+ }
}
-
- // Common intent-filter actions.
- xml::XmlNodeAction intentFilterAction;
- intentFilterAction["action"];
- intentFilterAction["category"];
- intentFilterAction["data"];
-
- // Common meta-data actions.
- xml::XmlNodeAction metaDataAction;
-
- // Manifest actions.
- xml::XmlNodeAction& manifestAction = (*executor)["manifest"];
- manifestAction.action(verifyManifest);
- manifestAction.action(fixCoreAppAttribute);
- manifestAction.action([&](xml::Element* el) -> bool {
- if (mOptions.versionNameDefault) {
- if (el->findAttribute(xml::kSchemaAndroid, "versionName") == nullptr) {
- el->attributes.push_back(xml::Attribute{
- xml::kSchemaAndroid,
- "versionName",
- mOptions.versionNameDefault.value() });
- }
- }
-
- if (mOptions.versionCodeDefault) {
- if (el->findAttribute(xml::kSchemaAndroid, "versionCode") == nullptr) {
- el->attributes.push_back(xml::Attribute{
- xml::kSchemaAndroid,
- "versionCode",
- mOptions.versionCodeDefault.value() });
- }
- }
- return true;
- });
-
- // Meta tags.
- manifestAction["eat-comment"];
-
- // Uses-sdk actions.
- manifestAction["uses-sdk"].action([&](xml::Element* el) -> bool {
- if (mOptions.minSdkVersionDefault &&
- el->findAttribute(xml::kSchemaAndroid, "minSdkVersion") == nullptr) {
- // There was no minSdkVersion defined and we have a default to assign.
- el->attributes.push_back(xml::Attribute{
- xml::kSchemaAndroid, "minSdkVersion",
- mOptions.minSdkVersionDefault.value() });
- }
-
- if (mOptions.targetSdkVersionDefault &&
- el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion") == nullptr) {
- // There was no targetSdkVersion defined and we have a default to assign.
- el->attributes.push_back(xml::Attribute{
- xml::kSchemaAndroid, "targetSdkVersion",
- mOptions.targetSdkVersionDefault.value() });
- }
- return true;
- });
-
- // Instrumentation actions.
- manifestAction["instrumentation"].action([&](xml::Element* el) -> bool {
- if (!mOptions.renameInstrumentationTargetPackage) {
- return true;
- }
-
- if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "targetPackage")) {
- attr->value = mOptions.renameInstrumentationTargetPackage.value();
- }
- return true;
- });
-
- manifestAction["original-package"];
- manifestAction["protected-broadcast"];
- manifestAction["uses-permission"];
- manifestAction["permission"];
- manifestAction["permission-tree"];
- manifestAction["permission-group"];
-
- manifestAction["uses-configuration"];
- manifestAction["uses-feature"];
- manifestAction["supports-screens"];
-
- manifestAction["compatible-screens"];
- manifestAction["compatible-screens"]["screen"];
-
- manifestAction["supports-gl-texture"];
-
- // Application actions.
- xml::XmlNodeAction& applicationAction = manifestAction["application"];
- applicationAction.action(optionalNameIsJavaClassName);
-
- // Uses library actions.
- applicationAction["uses-library"];
-
- // Meta-data.
- applicationAction["meta-data"] = metaDataAction;
-
- // Activity actions.
- applicationAction["activity"].action(requiredNameIsJavaClassName);
- applicationAction["activity"]["intent-filter"] = intentFilterAction;
- applicationAction["activity"]["meta-data"] = metaDataAction;
-
- // Activity alias actions.
- applicationAction["activity-alias"]["intent-filter"] = intentFilterAction;
- applicationAction["activity-alias"]["meta-data"] = metaDataAction;
-
- // Service actions.
- applicationAction["service"].action(requiredNameIsJavaClassName);
- applicationAction["service"]["intent-filter"] = intentFilterAction;
- applicationAction["service"]["meta-data"] = metaDataAction;
-
- // Receiver actions.
- applicationAction["receiver"].action(requiredNameIsJavaClassName);
- applicationAction["receiver"]["intent-filter"] = intentFilterAction;
- applicationAction["receiver"]["meta-data"] = metaDataAction;
-
- // Provider actions.
- applicationAction["provider"].action(requiredNameIsJavaClassName);
- applicationAction["provider"]["intent-filter"] = intentFilterAction;
- applicationAction["provider"]["meta-data"] = metaDataAction;
- applicationAction["provider"]["grant-uri-permissions"];
- applicationAction["provider"]["path-permissions"];
-
return true;
+ });
+
+ // Meta tags.
+ manifestAction["eat-comment"];
+
+ // Uses-sdk actions.
+ manifestAction["uses-sdk"].action([&](xml::Element* el) -> bool {
+ if (mOptions.minSdkVersionDefault &&
+ el->findAttribute(xml::kSchemaAndroid, "minSdkVersion") == nullptr) {
+ // There was no minSdkVersion defined and we have a default to assign.
+ el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "minSdkVersion",
+ mOptions.minSdkVersionDefault.value()});
+ }
+
+ if (mOptions.targetSdkVersionDefault &&
+ el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion") == nullptr) {
+ // There was no targetSdkVersion defined and we have a default to assign.
+ el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "targetSdkVersion",
+ mOptions.targetSdkVersionDefault.value()});
+ }
+ return true;
+ });
+
+ // Instrumentation actions.
+ manifestAction["instrumentation"].action([&](xml::Element* el) -> bool {
+ if (!mOptions.renameInstrumentationTargetPackage) {
+ return true;
+ }
+
+ if (xml::Attribute* attr =
+ el->findAttribute(xml::kSchemaAndroid, "targetPackage")) {
+ attr->value = mOptions.renameInstrumentationTargetPackage.value();
+ }
+ return true;
+ });
+
+ manifestAction["original-package"];
+ manifestAction["protected-broadcast"];
+ manifestAction["uses-permission"];
+ manifestAction["permission"];
+ manifestAction["permission-tree"];
+ manifestAction["permission-group"];
+
+ manifestAction["uses-configuration"];
+ manifestAction["uses-feature"];
+ manifestAction["supports-screens"];
+
+ manifestAction["compatible-screens"];
+ manifestAction["compatible-screens"]["screen"];
+
+ manifestAction["supports-gl-texture"];
+
+ // Application actions.
+ xml::XmlNodeAction& applicationAction = manifestAction["application"];
+ applicationAction.action(optionalNameIsJavaClassName);
+
+ // Uses library actions.
+ applicationAction["uses-library"];
+
+ // Meta-data.
+ applicationAction["meta-data"] = metaDataAction;
+
+ // Activity actions.
+ applicationAction["activity"].action(requiredNameIsJavaClassName);
+ applicationAction["activity"]["intent-filter"] = intentFilterAction;
+ applicationAction["activity"]["meta-data"] = metaDataAction;
+
+ // Activity alias actions.
+ applicationAction["activity-alias"]["intent-filter"] = intentFilterAction;
+ applicationAction["activity-alias"]["meta-data"] = metaDataAction;
+
+ // Service actions.
+ applicationAction["service"].action(requiredNameIsJavaClassName);
+ applicationAction["service"]["intent-filter"] = intentFilterAction;
+ applicationAction["service"]["meta-data"] = metaDataAction;
+
+ // Receiver actions.
+ applicationAction["receiver"].action(requiredNameIsJavaClassName);
+ applicationAction["receiver"]["intent-filter"] = intentFilterAction;
+ applicationAction["receiver"]["meta-data"] = metaDataAction;
+
+ // Provider actions.
+ applicationAction["provider"].action(requiredNameIsJavaClassName);
+ applicationAction["provider"]["intent-filter"] = intentFilterAction;
+ applicationAction["provider"]["meta-data"] = metaDataAction;
+ applicationAction["provider"]["grant-uri-permissions"];
+ applicationAction["provider"]["path-permissions"];
+
+ return true;
}
class FullyQualifiedClassNameVisitor : public xml::Visitor {
-public:
- using xml::Visitor::visit;
+ public:
+ using xml::Visitor::visit;
- explicit FullyQualifiedClassNameVisitor(const StringPiece& package) : mPackage(package) {
- }
+ explicit FullyQualifiedClassNameVisitor(const StringPiece& package)
+ : mPackage(package) {}
- void visit(xml::Element* el) override {
- for (xml::Attribute& attr : el->attributes) {
- if (attr.namespaceUri == xml::kSchemaAndroid
- && mClassAttributes.find(attr.name) != mClassAttributes.end()) {
- if (Maybe<std::string> newValue =
- util::getFullyQualifiedClassName(mPackage, attr.value)) {
- attr.value = std::move(newValue.value());
- }
- }
+ void visit(xml::Element* el) override {
+ for (xml::Attribute& attr : el->attributes) {
+ if (attr.namespaceUri == xml::kSchemaAndroid &&
+ mClassAttributes.find(attr.name) != mClassAttributes.end()) {
+ if (Maybe<std::string> newValue =
+ util::getFullyQualifiedClassName(mPackage, attr.value)) {
+ attr.value = std::move(newValue.value());
}
-
- // Super implementation to iterate over the children.
- xml::Visitor::visit(el);
+ }
}
-private:
- StringPiece mPackage;
- std::unordered_set<StringPiece> mClassAttributes = { "name" };
+ // Super implementation to iterate over the children.
+ xml::Visitor::visit(el);
+ }
+
+ private:
+ StringPiece mPackage;
+ std::unordered_set<StringPiece> mClassAttributes = {"name"};
};
-static bool renameManifestPackage(const StringPiece& packageOverride, xml::Element* manifestEl) {
- xml::Attribute* attr = manifestEl->findAttribute({}, "package");
+static bool renameManifestPackage(const StringPiece& packageOverride,
+ xml::Element* manifestEl) {
+ xml::Attribute* attr = manifestEl->findAttribute({}, "package");
- // We've already verified that the manifest element is present, with a package name specified.
- assert(attr);
+ // We've already verified that the manifest element is present, with a package
+ // name specified.
+ assert(attr);
- std::string originalPackage = std::move(attr->value);
- attr->value = packageOverride.toString();
+ std::string originalPackage = std::move(attr->value);
+ attr->value = packageOverride.toString();
- FullyQualifiedClassNameVisitor visitor(originalPackage);
- manifestEl->accept(&visitor);
- return true;
+ FullyQualifiedClassNameVisitor visitor(originalPackage);
+ manifestEl->accept(&visitor);
+ return true;
}
bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) {
- xml::Element* root = xml::findRootElement(doc->root.get());
- if (!root || !root->namespaceUri.empty() || root->name != "manifest") {
- context->getDiagnostics()->error(DiagMessage(doc->file.source)
- << "root tag must be <manifest>");
- return false;
- }
+ xml::Element* root = xml::findRootElement(doc->root.get());
+ if (!root || !root->namespaceUri.empty() || root->name != "manifest") {
+ context->getDiagnostics()->error(DiagMessage(doc->file.source)
+ << "root tag must be <manifest>");
+ return false;
+ }
- if ((mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault)
- && root->findChild({}, "uses-sdk") == nullptr) {
- // Auto insert a <uses-sdk> element.
- std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>();
- usesSdk->name = "uses-sdk";
- root->addChild(std::move(usesSdk));
- }
+ if ((mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault) &&
+ root->findChild({}, "uses-sdk") == nullptr) {
+ // Auto insert a <uses-sdk> element.
+ std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>();
+ usesSdk->name = "uses-sdk";
+ root->addChild(std::move(usesSdk));
+ }
- xml::XmlActionExecutor executor;
- if (!buildRules(&executor, context->getDiagnostics())) {
- return false;
- }
+ xml::XmlActionExecutor executor;
+ if (!buildRules(&executor, context->getDiagnostics())) {
+ return false;
+ }
- if (!executor.execute(xml::XmlActionExecutorPolicy::Whitelist, context->getDiagnostics(),
- doc)) {
- return false;
- }
+ if (!executor.execute(xml::XmlActionExecutorPolicy::Whitelist,
+ context->getDiagnostics(), doc)) {
+ return false;
+ }
- if (mOptions.renameManifestPackage) {
- // Rename manifest package outside of the XmlActionExecutor.
- // We need to extract the old package name and FullyQualify all class names.
- if (!renameManifestPackage(mOptions.renameManifestPackage.value(), root)) {
- return false;
- }
+ if (mOptions.renameManifestPackage) {
+ // Rename manifest package outside of the XmlActionExecutor.
+ // We need to extract the old package name and FullyQualify all class names.
+ if (!renameManifestPackage(mOptions.renameManifestPackage.value(), root)) {
+ return false;
}
- return true;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index 2e81266..c3a114b 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -27,12 +27,12 @@
namespace aapt {
struct ManifestFixerOptions {
- Maybe<std::string> minSdkVersionDefault;
- Maybe<std::string> targetSdkVersionDefault;
- Maybe<std::string> renameManifestPackage;
- Maybe<std::string> renameInstrumentationTargetPackage;
- Maybe<std::string> versionNameDefault;
- Maybe<std::string> versionCodeDefault;
+ Maybe<std::string> minSdkVersionDefault;
+ Maybe<std::string> targetSdkVersionDefault;
+ Maybe<std::string> renameManifestPackage;
+ Maybe<std::string> renameInstrumentationTargetPackage;
+ Maybe<std::string> versionNameDefault;
+ Maybe<std::string> versionCodeDefault;
};
/**
@@ -40,18 +40,18 @@
* where specified with ManifestFixerOptions.
*/
class ManifestFixer : public IXmlResourceConsumer {
-public:
- explicit ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) {
- }
+ public:
+ explicit ManifestFixer(const ManifestFixerOptions& options)
+ : mOptions(options) {}
- bool consume(IAaptContext* context, xml::XmlResource* doc) override;
+ bool consume(IAaptContext* context, xml::XmlResource* doc) override;
-private:
- bool buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag);
+ private:
+ bool buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag);
- ManifestFixerOptions mOptions;
+ ManifestFixerOptions mOptions;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_LINK_MANIFESTFIXER_H */
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 16ab9ab..dc78d98 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -23,254 +23,274 @@
namespace aapt {
struct ManifestFixerTest : public ::testing::Test {
- std::unique_ptr<IAaptContext> mContext;
+ std::unique_ptr<IAaptContext> mContext;
- void SetUp() override {
- mContext = test::ContextBuilder()
- .setCompilationPackage("android")
- .setPackageId(0x01)
- .setNameManglerPolicy(NameManglerPolicy{ "android" })
- .addSymbolSource(test::StaticSymbolSourceBuilder()
- .addSymbol("android:attr/package", ResourceId(0x01010000),
- test::AttributeBuilder()
- .setTypeMask(android::ResTable_map::TYPE_STRING)
- .build())
- .addSymbol("android:attr/minSdkVersion", ResourceId(0x01010001),
- test::AttributeBuilder()
- .setTypeMask(android::ResTable_map::TYPE_STRING |
- android::ResTable_map::TYPE_INTEGER)
- .build())
- .addSymbol("android:attr/targetSdkVersion", ResourceId(0x01010002),
- test::AttributeBuilder()
- .setTypeMask(android::ResTable_map::TYPE_STRING |
- android::ResTable_map::TYPE_INTEGER)
- .build())
- .addSymbol("android:string/str", ResourceId(0x01060000))
- .build())
- .build();
- }
+ void SetUp() override {
+ mContext =
+ test::ContextBuilder()
+ .setCompilationPackage("android")
+ .setPackageId(0x01)
+ .setNameManglerPolicy(NameManglerPolicy{"android"})
+ .addSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .addSymbol(
+ "android:attr/package", ResourceId(0x01010000),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_STRING)
+ .build())
+ .addSymbol(
+ "android:attr/minSdkVersion", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_STRING |
+ android::ResTable_map::TYPE_INTEGER)
+ .build())
+ .addSymbol(
+ "android:attr/targetSdkVersion", ResourceId(0x01010002),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_STRING |
+ android::ResTable_map::TYPE_INTEGER)
+ .build())
+ .addSymbol("android:string/str", ResourceId(0x01060000))
+ .build())
+ .build();
+ }
- std::unique_ptr<xml::XmlResource> verify(const StringPiece& str) {
- return verifyWithOptions(str, {});
- }
+ std::unique_ptr<xml::XmlResource> verify(const StringPiece& str) {
+ return verifyWithOptions(str, {});
+ }
- std::unique_ptr<xml::XmlResource> verifyWithOptions(const StringPiece& str,
- const ManifestFixerOptions& options) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(str);
- ManifestFixer fixer(options);
- if (fixer.consume(mContext.get(), doc.get())) {
- return doc;
- }
- return {};
+ std::unique_ptr<xml::XmlResource> verifyWithOptions(
+ const StringPiece& str, const ManifestFixerOptions& options) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(str);
+ ManifestFixer fixer(options);
+ if (fixer.consume(mContext.get(), doc.get())) {
+ return doc;
}
+ return {};
+ }
};
TEST_F(ManifestFixerTest, EnsureManifestIsRootTag) {
- EXPECT_EQ(nullptr, verify("<other-tag />"));
- EXPECT_EQ(nullptr, verify("<ns:manifest xmlns:ns=\"com\" />"));
- EXPECT_NE(nullptr, verify("<manifest package=\"android\"></manifest>"));
+ EXPECT_EQ(nullptr, verify("<other-tag />"));
+ EXPECT_EQ(nullptr, verify("<ns:manifest xmlns:ns=\"com\" />"));
+ EXPECT_NE(nullptr, verify("<manifest package=\"android\"></manifest>"));
}
TEST_F(ManifestFixerTest, EnsureManifestHasPackage) {
- EXPECT_NE(nullptr, verify("<manifest package=\"android\" />"));
- EXPECT_NE(nullptr, verify("<manifest package=\"com.android\" />"));
- EXPECT_NE(nullptr, verify("<manifest package=\"com.android.google\" />"));
- EXPECT_EQ(nullptr, verify("<manifest package=\"com.android.google.Class$1\" />"));
- EXPECT_EQ(nullptr,
- verify("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" "
- "android:package=\"com.android\" />"));
- EXPECT_EQ(nullptr, verify("<manifest package=\"@string/str\" />"));
+ EXPECT_NE(nullptr, verify("<manifest package=\"android\" />"));
+ EXPECT_NE(nullptr, verify("<manifest package=\"com.android\" />"));
+ EXPECT_NE(nullptr, verify("<manifest package=\"com.android.google\" />"));
+ EXPECT_EQ(nullptr,
+ verify("<manifest package=\"com.android.google.Class$1\" />"));
+ EXPECT_EQ(nullptr, verify("<manifest "
+ "xmlns:android=\"http://schemas.android.com/apk/"
+ "res/android\" "
+ "android:package=\"com.android\" />"));
+ EXPECT_EQ(nullptr, verify("<manifest package=\"@string/str\" />"));
}
TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
- ManifestFixerOptions options = { std::string("8"), std::string("22") };
+ ManifestFixerOptions options = {std::string("8"), std::string("22")};
- std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android">
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
- </manifest>)EOF", options);
- ASSERT_NE(nullptr, doc);
+ </manifest>)EOF",
+ options);
+ ASSERT_NE(nullptr, doc);
- xml::Element* el;
- xml::Attribute* attr;
+ xml::Element* el;
+ xml::Attribute* attr;
- el = xml::findRootElement(doc.get());
- ASSERT_NE(nullptr, el);
- el = el->findChild({}, "uses-sdk");
- ASSERT_NE(nullptr, el);
- attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ("7", attr->value);
- attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ("21", attr->value);
+ el = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, "uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("7", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("21", attr->value);
- doc = verifyWithOptions(R"EOF(
+ doc = verifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android">
<uses-sdk android:targetSdkVersion="21" />
- </manifest>)EOF", options);
- ASSERT_NE(nullptr, doc);
+ </manifest>)EOF",
+ options);
+ ASSERT_NE(nullptr, doc);
- el = xml::findRootElement(doc.get());
- ASSERT_NE(nullptr, el);
- el = el->findChild({}, "uses-sdk");
- ASSERT_NE(nullptr, el);
- attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ("8", attr->value);
- attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ("21", attr->value);
+ el = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, "uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("8", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("21", attr->value);
- doc = verifyWithOptions(R"EOF(
+ doc = verifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android">
<uses-sdk />
- </manifest>)EOF", options);
- ASSERT_NE(nullptr, doc);
+ </manifest>)EOF",
+ options);
+ ASSERT_NE(nullptr, doc);
- el = xml::findRootElement(doc.get());
- ASSERT_NE(nullptr, el);
- el = el->findChild({}, "uses-sdk");
- ASSERT_NE(nullptr, el);
- attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ("8", attr->value);
- attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ("22", attr->value);
+ el = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, "uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("8", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("22", attr->value);
- doc = verifyWithOptions(R"EOF(
+ doc = verifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android" />)EOF", options);
- ASSERT_NE(nullptr, doc);
+ package="android" />)EOF",
+ options);
+ ASSERT_NE(nullptr, doc);
- el = xml::findRootElement(doc.get());
- ASSERT_NE(nullptr, el);
- el = el->findChild({}, "uses-sdk");
- ASSERT_NE(nullptr, el);
- attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ("8", attr->value);
- attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ("22", attr->value);
+ el = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, "uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("8", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ("22", attr->value);
}
TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) {
- ManifestFixerOptions options;
- options.renameManifestPackage = std::string("com.android");
+ ManifestFixerOptions options;
+ options.renameManifestPackage = std::string("com.android");
- std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android">
<application android:name=".MainApplication" text="hello">
<activity android:name=".activity.Start" />
<receiver android:name="com.google.android.Receiver" />
</application>
- </manifest>)EOF", options);
- ASSERT_NE(nullptr, doc);
+ </manifest>)EOF",
+ options);
+ ASSERT_NE(nullptr, doc);
- xml::Element* manifestEl = xml::findRootElement(doc.get());
- ASSERT_NE(nullptr, manifestEl);
+ xml::Element* manifestEl = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, manifestEl);
- xml::Attribute* attr = nullptr;
+ xml::Attribute* attr = nullptr;
- attr = manifestEl->findAttribute({},"package");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(std::string("com.android"), attr->value);
+ attr = manifestEl->findAttribute({}, "package");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::string("com.android"), attr->value);
- xml::Element* applicationEl = manifestEl->findChild({}, "application");
- ASSERT_NE(nullptr, applicationEl);
+ xml::Element* applicationEl = manifestEl->findChild({}, "application");
+ ASSERT_NE(nullptr, applicationEl);
- attr = applicationEl->findAttribute(xml::kSchemaAndroid, "name");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(std::string("android.MainApplication"), attr->value);
+ attr = applicationEl->findAttribute(xml::kSchemaAndroid, "name");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::string("android.MainApplication"), attr->value);
- attr = applicationEl->findAttribute({}, "text");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(std::string("hello"), attr->value);
+ attr = applicationEl->findAttribute({}, "text");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::string("hello"), attr->value);
- xml::Element* el;
- el = applicationEl->findChild({}, "activity");
- ASSERT_NE(nullptr, el);
+ xml::Element* el;
+ el = applicationEl->findChild({}, "activity");
+ ASSERT_NE(nullptr, el);
- attr = el->findAttribute(xml::kSchemaAndroid, "name");
- ASSERT_NE(nullptr, el);
- EXPECT_EQ(std::string("android.activity.Start"), attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, "name");
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ(std::string("android.activity.Start"), attr->value);
- el = applicationEl->findChild({}, "receiver");
- ASSERT_NE(nullptr, el);
+ el = applicationEl->findChild({}, "receiver");
+ ASSERT_NE(nullptr, el);
- attr = el->findAttribute(xml::kSchemaAndroid, "name");
- ASSERT_NE(nullptr, el);
- EXPECT_EQ(std::string("com.google.android.Receiver"), attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, "name");
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ(std::string("com.google.android.Receiver"), attr->value);
}
-TEST_F(ManifestFixerTest, RenameManifestInstrumentationPackageAndFullyQualifyTarget) {
- ManifestFixerOptions options;
- options.renameInstrumentationTargetPackage = std::string("com.android");
+TEST_F(ManifestFixerTest,
+ RenameManifestInstrumentationPackageAndFullyQualifyTarget) {
+ ManifestFixerOptions options;
+ options.renameInstrumentationTargetPackage = std::string("com.android");
- std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android">
<instrumentation android:targetPackage="android" />
- </manifest>)EOF", options);
- ASSERT_NE(nullptr, doc);
+ </manifest>)EOF",
+ options);
+ ASSERT_NE(nullptr, doc);
- xml::Element* manifestEl = xml::findRootElement(doc.get());
- ASSERT_NE(nullptr, manifestEl);
+ xml::Element* manifestEl = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, manifestEl);
- xml::Element* instrumentationEl = manifestEl->findChild({}, "instrumentation");
- ASSERT_NE(nullptr, instrumentationEl);
+ xml::Element* instrumentationEl =
+ manifestEl->findChild({}, "instrumentation");
+ ASSERT_NE(nullptr, instrumentationEl);
- xml::Attribute* attr = instrumentationEl->findAttribute(xml::kSchemaAndroid, "targetPackage");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(std::string("com.android"), attr->value);
+ xml::Attribute* attr =
+ instrumentationEl->findAttribute(xml::kSchemaAndroid, "targetPackage");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::string("com.android"), attr->value);
}
TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) {
- ManifestFixerOptions options;
- options.versionNameDefault = std::string("Beta");
- options.versionCodeDefault = std::string("0x10000000");
+ ManifestFixerOptions options;
+ options.versionNameDefault = std::string("Beta");
+ options.versionCodeDefault = std::string("0x10000000");
- std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android" />)EOF", options);
- ASSERT_NE(nullptr, doc);
+ package="android" />)EOF",
+ options);
+ ASSERT_NE(nullptr, doc);
- xml::Element* manifestEl = xml::findRootElement(doc.get());
- ASSERT_NE(nullptr, manifestEl);
+ xml::Element* manifestEl = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, manifestEl);
- xml::Attribute* attr = manifestEl->findAttribute(xml::kSchemaAndroid, "versionName");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(std::string("Beta"), attr->value);
+ xml::Attribute* attr =
+ manifestEl->findAttribute(xml::kSchemaAndroid, "versionName");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::string("Beta"), attr->value);
- attr = manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(std::string("0x10000000"), attr->value);
+ attr = manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::string("0x10000000"), attr->value);
}
TEST_F(ManifestFixerTest, EnsureManifestAttributesAreTyped) {
- EXPECT_EQ(nullptr, verify("<manifest package=\"android\" coreApp=\"hello\" />"));
- EXPECT_EQ(nullptr, verify("<manifest package=\"android\" coreApp=\"1dp\" />"));
+ EXPECT_EQ(nullptr,
+ verify("<manifest package=\"android\" coreApp=\"hello\" />"));
+ EXPECT_EQ(nullptr,
+ verify("<manifest package=\"android\" coreApp=\"1dp\" />"));
- std::unique_ptr<xml::XmlResource> doc =
- verify("<manifest package=\"android\" coreApp=\"true\" />");
- ASSERT_NE(nullptr, doc);
+ std::unique_ptr<xml::XmlResource> doc =
+ verify("<manifest package=\"android\" coreApp=\"true\" />");
+ ASSERT_NE(nullptr, doc);
- xml::Element* el = xml::findRootElement(doc.get());
- ASSERT_NE(nullptr, el);
+ xml::Element* el = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
- EXPECT_EQ("manifest", el->name);
+ EXPECT_EQ("manifest", el->name);
- xml::Attribute* attr = el->findAttribute("", "coreApp");
- ASSERT_NE(nullptr, attr);
+ xml::Attribute* attr = el->findAttribute("", "coreApp");
+ ASSERT_NE(nullptr, attr);
- EXPECT_NE(nullptr, attr->compiledValue);
- EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(attr->compiledValue.get()));
+ EXPECT_NE(nullptr, attr->compiledValue);
+ EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(attr->compiledValue.get()));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
index 3c8af4f..174b41f 100644
--- a/tools/aapt2/link/PrivateAttributeMover.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -25,56 +25,61 @@
template <typename InputContainer, typename OutputIterator, typename Predicate>
OutputIterator moveIf(InputContainer& inputContainer, OutputIterator result,
Predicate pred) {
- const auto last = inputContainer.end();
- auto newEnd = std::find_if(inputContainer.begin(), inputContainer.end(), pred);
- if (newEnd == last) {
- return result;
- }
-
- *result = std::move(*newEnd);
-
- auto first = newEnd;
- ++first;
-
- for (; first != last; ++first) {
- if (bool(pred(*first))) {
- // We want to move this guy
- *result = std::move(*first);
- ++result;
- } else {
- // We want to keep this guy, but we will need to move it up the list to replace
- // missing items.
- *newEnd = std::move(*first);
- ++newEnd;
- }
- }
-
- inputContainer.erase(newEnd, last);
+ const auto last = inputContainer.end();
+ auto newEnd =
+ std::find_if(inputContainer.begin(), inputContainer.end(), pred);
+ if (newEnd == last) {
return result;
-}
+ }
-bool PrivateAttributeMover::consume(IAaptContext* context, ResourceTable* table) {
- for (auto& package : table->packages) {
- ResourceTableType* type = package->findType(ResourceType::kAttr);
- if (!type) {
- continue;
- }
+ *result = std::move(*newEnd);
- if (type->symbolStatus.state != SymbolState::kPublic) {
- // No public attributes, so we can safely leave these private attributes where they are.
- return true;
- }
+ auto first = newEnd;
+ ++first;
- ResourceTableType* privAttrType = package->findOrCreateType(ResourceType::kAttrPrivate);
- assert(privAttrType->entries.empty());
-
- moveIf(type->entries, std::back_inserter(privAttrType->entries),
- [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
- return entry->symbolStatus.state != SymbolState::kPublic;
- });
- break;
+ for (; first != last; ++first) {
+ if (bool(pred(*first))) {
+ // We want to move this guy
+ *result = std::move(*first);
+ ++result;
+ } else {
+ // We want to keep this guy, but we will need to move it up the list to
+ // replace
+ // missing items.
+ *newEnd = std::move(*first);
+ ++newEnd;
}
- return true;
+ }
+
+ inputContainer.erase(newEnd, last);
+ return result;
}
-} // namespace aapt
+bool PrivateAttributeMover::consume(IAaptContext* context,
+ ResourceTable* table) {
+ for (auto& package : table->packages) {
+ ResourceTableType* type = package->findType(ResourceType::kAttr);
+ if (!type) {
+ continue;
+ }
+
+ if (type->symbolStatus.state != SymbolState::kPublic) {
+ // No public attributes, so we can safely leave these private attributes
+ // where they are.
+ return true;
+ }
+
+ ResourceTableType* privAttrType =
+ package->findOrCreateType(ResourceType::kAttrPrivate);
+ assert(privAttrType->entries.empty());
+
+ moveIf(type->entries, std::back_inserter(privAttrType->entries),
+ [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
+ return entry->symbolStatus.state != SymbolState::kPublic;
+ });
+ break;
+ }
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
index c9d1a08..a7a1013 100644
--- a/tools/aapt2/link/PrivateAttributeMover_test.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -20,56 +20,60 @@
namespace aapt {
TEST(PrivateAttributeMoverTest, MovePrivateAttributes) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addSimple("android:attr/publicA")
- .addSimple("android:attr/privateA")
- .addSimple("android:attr/publicB")
- .addSimple("android:attr/privateB")
- .setSymbolState("android:attr/publicA", ResourceId(0x01010000), SymbolState::kPublic)
- .setSymbolState("android:attr/publicB", ResourceId(0x01010000), SymbolState::kPublic)
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .addSimple("android:attr/publicA")
+ .addSimple("android:attr/privateA")
+ .addSimple("android:attr/publicB")
+ .addSimple("android:attr/privateB")
+ .setSymbolState("android:attr/publicA", ResourceId(0x01010000),
+ SymbolState::kPublic)
+ .setSymbolState("android:attr/publicB", ResourceId(0x01010000),
+ SymbolState::kPublic)
+ .build();
- PrivateAttributeMover mover;
- ASSERT_TRUE(mover.consume(context.get(), table.get()));
+ PrivateAttributeMover mover;
+ ASSERT_TRUE(mover.consume(context.get(), table.get()));
- ResourceTablePackage* package = table->findPackage("android");
- ASSERT_NE(package, nullptr);
+ ResourceTablePackage* package = table->findPackage("android");
+ ASSERT_NE(package, nullptr);
- ResourceTableType* type = package->findType(ResourceType::kAttr);
- ASSERT_NE(type, nullptr);
- ASSERT_EQ(type->entries.size(), 2u);
- EXPECT_NE(type->findEntry("publicA"), nullptr);
- EXPECT_NE(type->findEntry("publicB"), nullptr);
+ ResourceTableType* type = package->findType(ResourceType::kAttr);
+ ASSERT_NE(type, nullptr);
+ ASSERT_EQ(type->entries.size(), 2u);
+ EXPECT_NE(type->findEntry("publicA"), nullptr);
+ EXPECT_NE(type->findEntry("publicB"), nullptr);
- type = package->findType(ResourceType::kAttrPrivate);
- ASSERT_NE(type, nullptr);
- ASSERT_EQ(type->entries.size(), 2u);
- EXPECT_NE(type->findEntry("privateA"), nullptr);
- EXPECT_NE(type->findEntry("privateB"), nullptr);
+ type = package->findType(ResourceType::kAttrPrivate);
+ ASSERT_NE(type, nullptr);
+ ASSERT_EQ(type->entries.size(), 2u);
+ EXPECT_NE(type->findEntry("privateA"), nullptr);
+ EXPECT_NE(type->findEntry("privateB"), nullptr);
}
-TEST(PrivateAttributeMoverTest, LeavePrivateAttributesWhenNoPublicAttributesDefined) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+TEST(PrivateAttributeMoverTest,
+ LeavePrivateAttributesWhenNoPublicAttributesDefined) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addSimple("android:attr/privateA")
- .addSimple("android:attr/privateB")
- .build();
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .addSimple("android:attr/privateA")
+ .addSimple("android:attr/privateB")
+ .build();
- PrivateAttributeMover mover;
- ASSERT_TRUE(mover.consume(context.get(), table.get()));
+ PrivateAttributeMover mover;
+ ASSERT_TRUE(mover.consume(context.get(), table.get()));
- ResourceTablePackage* package = table->findPackage("android");
- ASSERT_NE(package, nullptr);
+ ResourceTablePackage* package = table->findPackage("android");
+ ASSERT_NE(package, nullptr);
- ResourceTableType* type = package->findType(ResourceType::kAttr);
- ASSERT_NE(type, nullptr);
- ASSERT_EQ(type->entries.size(), 2u);
+ ResourceTableType* type = package->findType(ResourceType::kAttr);
+ ASSERT_NE(type, nullptr);
+ ASSERT_EQ(type->entries.size(), 2u);
- type = package->findType(ResourceType::kAttrPrivate);
- ASSERT_EQ(type, nullptr);
+ type = package->findType(ResourceType::kAttrPrivate);
+ ASSERT_EQ(type, nullptr);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/ProductFilter.cpp b/tools/aapt2/link/ProductFilter.cpp
index 8784e89..d59b682 100644
--- a/tools/aapt2/link/ProductFilter.cpp
+++ b/tools/aapt2/link/ProductFilter.cpp
@@ -18,101 +18,103 @@
namespace aapt {
-ProductFilter::ResourceConfigValueIter
-ProductFilter::selectProductToKeep(const ResourceNameRef& name,
- const ResourceConfigValueIter begin,
- const ResourceConfigValueIter end,
- IDiagnostics* diag) {
- ResourceConfigValueIter defaultProductIter = end;
- ResourceConfigValueIter selectedProductIter = end;
+ProductFilter::ResourceConfigValueIter ProductFilter::selectProductToKeep(
+ const ResourceNameRef& name, const ResourceConfigValueIter begin,
+ const ResourceConfigValueIter end, IDiagnostics* diag) {
+ ResourceConfigValueIter defaultProductIter = end;
+ ResourceConfigValueIter selectedProductIter = end;
- for (ResourceConfigValueIter iter = begin; iter != end; ++iter) {
- ResourceConfigValue* configValue = iter->get();
- if (mProducts.find(configValue->product) != mProducts.end()) {
- if (selectedProductIter != end) {
- // We have two possible values for this product!
- diag->error(DiagMessage(configValue->value->getSource())
- << "selection of product '" << configValue->product
- << "' for resource " << name << " is ambiguous");
+ for (ResourceConfigValueIter iter = begin; iter != end; ++iter) {
+ ResourceConfigValue* configValue = iter->get();
+ if (mProducts.find(configValue->product) != mProducts.end()) {
+ if (selectedProductIter != end) {
+ // We have two possible values for this product!
+ diag->error(DiagMessage(configValue->value->getSource())
+ << "selection of product '" << configValue->product
+ << "' for resource " << name << " is ambiguous");
- ResourceConfigValue* previouslySelectedConfigValue = selectedProductIter->get();
- diag->note(DiagMessage(previouslySelectedConfigValue->value->getSource())
- << "product '" << previouslySelectedConfigValue->product
- << "' is also a candidate");
- return end;
- }
-
- // Select this product.
- selectedProductIter = iter;
- }
-
- if (configValue->product.empty() || configValue->product == "default") {
- if (defaultProductIter != end) {
- // We have two possible default values.
- diag->error(DiagMessage(configValue->value->getSource())
- << "multiple default products defined for resource " << name);
-
- ResourceConfigValue* previouslyDefaultConfigValue = defaultProductIter->get();
- diag->note(DiagMessage(previouslyDefaultConfigValue->value->getSource())
- << "default product also defined here");
- return end;
- }
-
- // Mark the default.
- defaultProductIter = iter;
- }
- }
-
- if (defaultProductIter == end) {
- diag->error(DiagMessage() << "no default product defined for resource " << name);
+ ResourceConfigValue* previouslySelectedConfigValue =
+ selectedProductIter->get();
+ diag->note(
+ DiagMessage(previouslySelectedConfigValue->value->getSource())
+ << "product '" << previouslySelectedConfigValue->product
+ << "' is also a candidate");
return end;
+ }
+
+ // Select this product.
+ selectedProductIter = iter;
}
- if (selectedProductIter == end) {
- selectedProductIter = defaultProductIter;
+ if (configValue->product.empty() || configValue->product == "default") {
+ if (defaultProductIter != end) {
+ // We have two possible default values.
+ diag->error(DiagMessage(configValue->value->getSource())
+ << "multiple default products defined for resource "
+ << name);
+
+ ResourceConfigValue* previouslyDefaultConfigValue =
+ defaultProductIter->get();
+ diag->note(DiagMessage(previouslyDefaultConfigValue->value->getSource())
+ << "default product also defined here");
+ return end;
+ }
+
+ // Mark the default.
+ defaultProductIter = iter;
}
- return selectedProductIter;
+ }
+
+ if (defaultProductIter == end) {
+ diag->error(DiagMessage() << "no default product defined for resource "
+ << name);
+ return end;
+ }
+
+ if (selectedProductIter == end) {
+ selectedProductIter = defaultProductIter;
+ }
+ return selectedProductIter;
}
bool ProductFilter::consume(IAaptContext* context, ResourceTable* table) {
- bool error = false;
- for (auto& pkg : table->packages) {
- for (auto& type : pkg->types) {
- for (auto& entry : type->entries) {
- std::vector<std::unique_ptr<ResourceConfigValue>> newValues;
+ bool error = false;
+ for (auto& pkg : table->packages) {
+ for (auto& type : pkg->types) {
+ for (auto& entry : type->entries) {
+ std::vector<std::unique_ptr<ResourceConfigValue>> newValues;
- ResourceConfigValueIter iter = entry->values.begin();
- ResourceConfigValueIter startRangeIter = iter;
- while (iter != entry->values.end()) {
- ++iter;
- if (iter == entry->values.end() ||
- (*iter)->config != (*startRangeIter)->config) {
-
- // End of the array, or we saw a different config,
- // so this must be the end of a range of products.
- // Select the product to keep from the set of products defined.
- ResourceNameRef name(pkg->name, type->type, entry->name);
- auto valueToKeep = selectProductToKeep(name, startRangeIter, iter,
- context->getDiagnostics());
- if (valueToKeep == iter) {
- // An error occurred, we could not pick a product.
- error = true;
- } else {
- // We selected a product to keep. Move it to the new array.
- newValues.push_back(std::move(*valueToKeep));
- }
-
- // Start the next range of products.
- startRangeIter = iter;
- }
- }
-
- // Now move the new values in to place.
- entry->values = std::move(newValues);
+ ResourceConfigValueIter iter = entry->values.begin();
+ ResourceConfigValueIter startRangeIter = iter;
+ while (iter != entry->values.end()) {
+ ++iter;
+ if (iter == entry->values.end() ||
+ (*iter)->config != (*startRangeIter)->config) {
+ // End of the array, or we saw a different config,
+ // so this must be the end of a range of products.
+ // Select the product to keep from the set of products defined.
+ ResourceNameRef name(pkg->name, type->type, entry->name);
+ auto valueToKeep = selectProductToKeep(name, startRangeIter, iter,
+ context->getDiagnostics());
+ if (valueToKeep == iter) {
+ // An error occurred, we could not pick a product.
+ error = true;
+ } else {
+ // We selected a product to keep. Move it to the new array.
+ newValues.push_back(std::move(*valueToKeep));
}
+
+ // Start the next range of products.
+ startRangeIter = iter;
+ }
}
+
+ // Now move the new values in to place.
+ entry->values = std::move(newValues);
+ }
}
- return !error;
+ }
+ return !error;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/ProductFilter.h b/tools/aapt2/link/ProductFilter.h
index 7724e14..cc8b8c2 100644
--- a/tools/aapt2/link/ProductFilter.h
+++ b/tools/aapt2/link/ProductFilter.h
@@ -26,24 +26,25 @@
namespace aapt {
class ProductFilter {
-public:
- using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator;
+ public:
+ using ResourceConfigValueIter =
+ std::vector<std::unique_ptr<ResourceConfigValue>>::iterator;
- explicit ProductFilter(std::unordered_set<std::string> products) : mProducts(products) { }
+ explicit ProductFilter(std::unordered_set<std::string> products)
+ : mProducts(products) {}
- ResourceConfigValueIter selectProductToKeep(const ResourceNameRef& name,
- const ResourceConfigValueIter begin,
- const ResourceConfigValueIter end,
- IDiagnostics* diag);
+ ResourceConfigValueIter selectProductToKeep(
+ const ResourceNameRef& name, const ResourceConfigValueIter begin,
+ const ResourceConfigValueIter end, IDiagnostics* diag);
- bool consume(IAaptContext* context, ResourceTable* table);
+ bool consume(IAaptContext* context, ResourceTable* table);
-private:
- std::unordered_set<std::string> mProducts;
+ private:
+ std::unordered_set<std::string> mProducts;
- DISALLOW_COPY_AND_ASSIGN(ProductFilter);
+ DISALLOW_COPY_AND_ASSIGN(ProductFilter);
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_LINK_PRODUCTFILTER_H */
diff --git a/tools/aapt2/link/ProductFilter_test.cpp b/tools/aapt2/link/ProductFilter_test.cpp
index a3376ac..7f78f8b 100644
--- a/tools/aapt2/link/ProductFilter_test.cpp
+++ b/tools/aapt2/link/ProductFilter_test.cpp
@@ -20,114 +20,110 @@
namespace aapt {
TEST(ProductFilterTest, SelectTwoProducts) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- const ConfigDescription land = test::parseConfigOrDie("land");
- const ConfigDescription port = test::parseConfigOrDie("port");
+ const ConfigDescription land = test::parseConfigOrDie("land");
+ const ConfigDescription port = test::parseConfigOrDie("port");
- ResourceTable table;
- ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
- land, "",
- test::ValueBuilder<Id>()
- .setSource(Source("land/default.xml")).build(),
- context->getDiagnostics()));
- ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
- land, "tablet",
- test::ValueBuilder<Id>()
- .setSource(Source("land/tablet.xml")).build(),
- context->getDiagnostics()));
+ ResourceTable table;
+ ASSERT_TRUE(table.addResource(
+ test::parseNameOrDie("android:string/one"), land, "",
+ test::ValueBuilder<Id>().setSource(Source("land/default.xml")).build(),
+ context->getDiagnostics()));
+ ASSERT_TRUE(table.addResource(
+ test::parseNameOrDie("android:string/one"), land, "tablet",
+ test::ValueBuilder<Id>().setSource(Source("land/tablet.xml")).build(),
+ context->getDiagnostics()));
- ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
- port, "",
- test::ValueBuilder<Id>()
- .setSource(Source("port/default.xml")).build(),
- context->getDiagnostics()));
- ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
- port, "tablet",
- test::ValueBuilder<Id>()
- .setSource(Source("port/tablet.xml")).build(),
- context->getDiagnostics()));
+ ASSERT_TRUE(table.addResource(
+ test::parseNameOrDie("android:string/one"), port, "",
+ test::ValueBuilder<Id>().setSource(Source("port/default.xml")).build(),
+ context->getDiagnostics()));
+ ASSERT_TRUE(table.addResource(
+ test::parseNameOrDie("android:string/one"), port, "tablet",
+ test::ValueBuilder<Id>().setSource(Source("port/tablet.xml")).build(),
+ context->getDiagnostics()));
- ProductFilter filter({ "tablet" });
- ASSERT_TRUE(filter.consume(context.get(), &table));
+ ProductFilter filter({"tablet"});
+ ASSERT_TRUE(filter.consume(context.get(), &table));
- EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one",
- land, ""));
- EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one",
- land, "tablet"));
- EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one",
- port, ""));
- EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one",
- port, "tablet"));
+ EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(
+ &table, "android:string/one", land, ""));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(
+ &table, "android:string/one", land, "tablet"));
+ EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(
+ &table, "android:string/one", port, ""));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(
+ &table, "android:string/one", port, "tablet"));
}
TEST(ProductFilterTest, SelectDefaultProduct) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- ResourceTable table;
- ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
- ConfigDescription::defaultConfig(), "",
- test::ValueBuilder<Id>()
- .setSource(Source("default.xml")).build(),
- context->getDiagnostics()));
- ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
- ConfigDescription::defaultConfig(), "tablet",
- test::ValueBuilder<Id>()
- .setSource(Source("tablet.xml")).build(),
- context->getDiagnostics()));
+ ResourceTable table;
+ ASSERT_TRUE(table.addResource(
+ test::parseNameOrDie("android:string/one"),
+ ConfigDescription::defaultConfig(), "",
+ test::ValueBuilder<Id>().setSource(Source("default.xml")).build(),
+ context->getDiagnostics()));
+ ASSERT_TRUE(table.addResource(
+ test::parseNameOrDie("android:string/one"),
+ ConfigDescription::defaultConfig(), "tablet",
+ test::ValueBuilder<Id>().setSource(Source("tablet.xml")).build(),
+ context->getDiagnostics()));
- ProductFilter filter({});
- ASSERT_TRUE(filter.consume(context.get(), &table));
+ ProductFilter filter({});
+ ASSERT_TRUE(filter.consume(context.get(), &table));
- EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one",
- ConfigDescription::defaultConfig(),
- ""));
- EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one",
- ConfigDescription::defaultConfig(),
- "tablet"));
+ EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(
+ &table, "android:string/one",
+ ConfigDescription::defaultConfig(), ""));
+ EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(
+ &table, "android:string/one",
+ ConfigDescription::defaultConfig(), "tablet"));
}
TEST(ProductFilterTest, FailOnAmbiguousProduct) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- ResourceTable table;
- ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
- ConfigDescription::defaultConfig(), "",
- test::ValueBuilder<Id>()
- .setSource(Source("default.xml")).build(),
- context->getDiagnostics()));
- ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
- ConfigDescription::defaultConfig(), "tablet",
- test::ValueBuilder<Id>()
- .setSource(Source("tablet.xml")).build(),
- context->getDiagnostics()));
- ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
- ConfigDescription::defaultConfig(), "no-sdcard",
- test::ValueBuilder<Id>()
- .setSource(Source("no-sdcard.xml")).build(),
- context->getDiagnostics()));
+ ResourceTable table;
+ ASSERT_TRUE(table.addResource(
+ test::parseNameOrDie("android:string/one"),
+ ConfigDescription::defaultConfig(), "",
+ test::ValueBuilder<Id>().setSource(Source("default.xml")).build(),
+ context->getDiagnostics()));
+ ASSERT_TRUE(table.addResource(
+ test::parseNameOrDie("android:string/one"),
+ ConfigDescription::defaultConfig(), "tablet",
+ test::ValueBuilder<Id>().setSource(Source("tablet.xml")).build(),
+ context->getDiagnostics()));
+ ASSERT_TRUE(table.addResource(
+ test::parseNameOrDie("android:string/one"),
+ ConfigDescription::defaultConfig(), "no-sdcard",
+ test::ValueBuilder<Id>().setSource(Source("no-sdcard.xml")).build(),
+ context->getDiagnostics()));
- ProductFilter filter({ "tablet", "no-sdcard" });
- ASSERT_FALSE(filter.consume(context.get(), &table));
+ ProductFilter filter({"tablet", "no-sdcard"});
+ ASSERT_FALSE(filter.consume(context.get(), &table));
}
TEST(ProductFilterTest, FailOnMultipleDefaults) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- ResourceTable table;
- ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
- ConfigDescription::defaultConfig(), "",
- test::ValueBuilder<Id>()
- .setSource(Source(".xml")).build(),
- context->getDiagnostics()));
- ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
- ConfigDescription::defaultConfig(), "default",
- test::ValueBuilder<Id>()
- .setSource(Source("default.xml")).build(),
- context->getDiagnostics()));
+ ResourceTable table;
+ ASSERT_TRUE(table.addResource(
+ test::parseNameOrDie("android:string/one"),
+ ConfigDescription::defaultConfig(), "",
+ test::ValueBuilder<Id>().setSource(Source(".xml")).build(),
+ context->getDiagnostics()));
+ ASSERT_TRUE(table.addResource(
+ test::parseNameOrDie("android:string/one"),
+ ConfigDescription::defaultConfig(), "default",
+ test::ValueBuilder<Id>().setSource(Source("default.xml")).build(),
+ context->getDiagnostics()));
- ProductFilter filter({});
- ASSERT_FALSE(filter.consume(context.get(), &table));
+ ProductFilter filter({});
+ ASSERT_FALSE(filter.consume(context.get(), &table));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index be7aca3..7fe0956 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#include "Diagnostics.h"
#include "ReferenceLinker.h"
+#include "Diagnostics.h"
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
@@ -34,302 +34,335 @@
namespace {
/**
- * The ReferenceLinkerVisitor will follow all references and make sure they point
- * to resources that actually exist, either in the local resource table, or as external
- * symbols. Once the target resource has been found, the ID of the resource will be assigned
+ * The ReferenceLinkerVisitor will follow all references and make sure they
+ * point
+ * to resources that actually exist, either in the local resource table, or as
+ * external
+ * symbols. Once the target resource has been found, the ID of the resource will
+ * be assigned
* to the reference object.
*
* NOTE: All of the entries in the ResourceTable must be assigned IDs.
*/
class ReferenceLinkerVisitor : public ValueVisitor {
-public:
- using ValueVisitor::visit;
+ public:
+ using ValueVisitor::visit;
- ReferenceLinkerVisitor(IAaptContext* context, SymbolTable* symbols, StringPool* stringPool,
- xml::IPackageDeclStack* decl,CallSite* callSite) :
- mContext(context), mSymbols(symbols), mPackageDecls(decl), mStringPool(stringPool),
- mCallSite(callSite) {
+ ReferenceLinkerVisitor(IAaptContext* context, SymbolTable* symbols,
+ StringPool* stringPool, xml::IPackageDeclStack* decl,
+ CallSite* callSite)
+ : mContext(context),
+ mSymbols(symbols),
+ mPackageDecls(decl),
+ mStringPool(stringPool),
+ mCallSite(callSite) {}
+
+ void visit(Reference* ref) override {
+ if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mPackageDecls,
+ mCallSite)) {
+ mError = true;
+ }
+ }
+
+ /**
+ * We visit the Style specially because during this phase, values of
+ * attributes are
+ * all RawString values. Now that we are expected to resolve all symbols, we
+ * can
+ * lookup the attributes to find out which types are allowed for the
+ * attributes' values.
+ */
+ void visit(Style* style) override {
+ if (style->parent) {
+ visit(&style->parent.value());
}
- void visit(Reference* ref) override {
- if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mPackageDecls, mCallSite)) {
- mError = true;
- }
- }
+ for (Style::Entry& entry : style->entries) {
+ std::string errStr;
- /**
- * We visit the Style specially because during this phase, values of attributes are
- * all RawString values. Now that we are expected to resolve all symbols, we can
- * lookup the attributes to find out which types are allowed for the attributes' values.
- */
- void visit(Style* style) override {
- if (style->parent) {
- visit(&style->parent.value());
+ // Transform the attribute reference so that it is using the fully
+ // qualified package
+ // name. This will also mark the reference as being able to see private
+ // resources if
+ // there was a '*' in the reference or if the package came from the
+ // private namespace.
+ Reference transformedReference = entry.key;
+ transformReferenceFromNamespace(mPackageDecls,
+ mContext->getCompilationPackage(),
+ &transformedReference);
+
+ // Find the attribute in the symbol table and check if it is visible from
+ // this callsite.
+ const SymbolTable::Symbol* symbol =
+ ReferenceLinker::resolveAttributeCheckVisibility(
+ transformedReference, mContext->getNameMangler(), mSymbols,
+ mCallSite, &errStr);
+ if (symbol) {
+ // Assign our style key the correct ID.
+ // The ID may not exist.
+ entry.key.id = symbol->id;
+
+ // Try to convert the value to a more specific, typed value based on the
+ // attribute it is set to.
+ entry.value = parseValueWithAttribute(std::move(entry.value),
+ symbol->attribute.get());
+
+ // Link/resolve the final value (mostly if it's a reference).
+ entry.value->accept(this);
+
+ // Now verify that the type of this item is compatible with the
+ // attribute it
+ // is defined for. We pass `nullptr` as the DiagMessage so that this
+ // check is
+ // fast and we avoid creating a DiagMessage when the match is
+ // successful.
+ if (!symbol->attribute->matches(entry.value.get(), nullptr)) {
+ // The actual type of this item is incompatible with the attribute.
+ DiagMessage msg(entry.key.getSource());
+
+ // Call the matches method again, this time with a DiagMessage so we
+ // fill
+ // in the actual error message.
+ symbol->attribute->matches(entry.value.get(), &msg);
+ mContext->getDiagnostics()->error(msg);
+ mError = true;
}
- for (Style::Entry& entry : style->entries) {
- std::string errStr;
+ } else {
+ DiagMessage msg(entry.key.getSource());
+ msg << "style attribute '";
+ ReferenceLinker::writeResourceName(&msg, entry.key,
+ transformedReference);
+ msg << "' " << errStr;
+ mContext->getDiagnostics()->error(msg);
+ mError = true;
+ }
+ }
+ }
- // Transform the attribute reference so that it is using the fully qualified package
- // name. This will also mark the reference as being able to see private resources if
- // there was a '*' in the reference or if the package came from the private namespace.
- Reference transformedReference = entry.key;
- transformReferenceFromNamespace(mPackageDecls, mContext->getCompilationPackage(),
- &transformedReference);
+ bool hasError() { return mError; }
- // Find the attribute in the symbol table and check if it is visible from this callsite.
- const SymbolTable::Symbol* symbol = ReferenceLinker::resolveAttributeCheckVisibility(
- transformedReference, mContext->getNameMangler(), mSymbols, mCallSite, &errStr);
- if (symbol) {
- // Assign our style key the correct ID.
- // The ID may not exist.
- entry.key.id = symbol->id;
+ private:
+ IAaptContext* mContext;
+ SymbolTable* mSymbols;
+ xml::IPackageDeclStack* mPackageDecls;
+ StringPool* mStringPool;
+ CallSite* mCallSite;
+ bool mError = false;
- // Try to convert the value to a more specific, typed value based on the
- // attribute it is set to.
- entry.value = parseValueWithAttribute(std::move(entry.value),
- symbol->attribute.get());
+ /**
+ * Transform a RawString value into a more specific, appropriate value, based
+ * on the
+ * Attribute. If a non RawString value is passed in, this is an identity
+ * transform.
+ */
+ std::unique_ptr<Item> parseValueWithAttribute(std::unique_ptr<Item> value,
+ const Attribute* attr) {
+ if (RawString* rawString = valueCast<RawString>(value.get())) {
+ std::unique_ptr<Item> transformed =
+ ResourceUtils::tryParseItemForAttribute(*rawString->value, attr);
- // Link/resolve the final value (mostly if it's a reference).
- entry.value->accept(this);
-
- // Now verify that the type of this item is compatible with the attribute it
- // is defined for. We pass `nullptr` as the DiagMessage so that this check is
- // fast and we avoid creating a DiagMessage when the match is successful.
- if (!symbol->attribute->matches(entry.value.get(), nullptr)) {
- // The actual type of this item is incompatible with the attribute.
- DiagMessage msg(entry.key.getSource());
-
- // Call the matches method again, this time with a DiagMessage so we fill
- // in the actual error message.
- symbol->attribute->matches(entry.value.get(), &msg);
- mContext->getDiagnostics()->error(msg);
- mError = true;
- }
-
- } else {
- DiagMessage msg(entry.key.getSource());
- msg << "style attribute '";
- ReferenceLinker::writeResourceName(&msg, entry.key, transformedReference);
- msg << "' " << errStr;
- mContext->getDiagnostics()->error(msg);
- mError = true;
- }
+ // If we could not parse as any specific type, try a basic STRING.
+ if (!transformed &&
+ (attr->typeMask & android::ResTable_map::TYPE_STRING)) {
+ util::StringBuilder stringBuilder;
+ stringBuilder.append(*rawString->value);
+ if (stringBuilder) {
+ transformed = util::make_unique<String>(
+ mStringPool->makeRef(stringBuilder.str()));
}
- }
+ }
- bool hasError() {
- return mError;
- }
-
-private:
- IAaptContext* mContext;
- SymbolTable* mSymbols;
- xml::IPackageDeclStack* mPackageDecls;
- StringPool* mStringPool;
- CallSite* mCallSite;
- bool mError = false;
-
- /**
- * Transform a RawString value into a more specific, appropriate value, based on the
- * Attribute. If a non RawString value is passed in, this is an identity transform.
- */
- std::unique_ptr<Item> parseValueWithAttribute(std::unique_ptr<Item> value,
- const Attribute* attr) {
- if (RawString* rawString = valueCast<RawString>(value.get())) {
- std::unique_ptr<Item> transformed =
- ResourceUtils::tryParseItemForAttribute(*rawString->value, attr);
-
- // If we could not parse as any specific type, try a basic STRING.
- if (!transformed && (attr->typeMask & android::ResTable_map::TYPE_STRING)) {
- util::StringBuilder stringBuilder;
- stringBuilder.append(*rawString->value);
- if (stringBuilder) {
- transformed = util::make_unique<String>(
- mStringPool->makeRef(stringBuilder.str()));
- }
- }
-
- if (transformed) {
- return transformed;
- }
- };
- return value;
- }
+ if (transformed) {
+ return transformed;
+ }
+ };
+ return value;
+ }
};
-} // namespace
+} // namespace
/**
- * The symbol is visible if it is public, or if the reference to it is requesting private access
+ * The symbol is visible if it is public, or if the reference to it is
+ * requesting private access
* or if the callsite comes from the same package.
*/
-bool ReferenceLinker::isSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref,
+bool ReferenceLinker::isSymbolVisible(const SymbolTable::Symbol& symbol,
+ const Reference& ref,
const CallSite& callSite) {
- if (!symbol.isPublic && !ref.privateReference) {
- if (ref.name) {
- return callSite.resource.package == ref.name.value().package;
- } else if (ref.id && symbol.id) {
- return ref.id.value().packageId() == symbol.id.value().packageId();
- } else {
- return false;
- }
+ if (!symbol.isPublic && !ref.privateReference) {
+ if (ref.name) {
+ return callSite.resource.package == ref.name.value().package;
+ } else if (ref.id && symbol.id) {
+ return ref.id.value().packageId() == symbol.id.value().packageId();
+ } else {
+ return false;
}
- return true;
+ }
+ return true;
}
-const SymbolTable::Symbol* ReferenceLinker::resolveSymbol(const Reference& reference,
- NameMangler* mangler,
- SymbolTable* symbols) {
- if (reference.name) {
- Maybe<ResourceName> mangled = mangler->mangleName(reference.name.value());
- return symbols->findByName(mangled ? mangled.value() : reference.name.value());
- } else if (reference.id) {
- return symbols->findById(reference.id.value());
- } else {
- return nullptr;
- }
+const SymbolTable::Symbol* ReferenceLinker::resolveSymbol(
+ const Reference& reference, NameMangler* mangler, SymbolTable* symbols) {
+ if (reference.name) {
+ Maybe<ResourceName> mangled = mangler->mangleName(reference.name.value());
+ return symbols->findByName(mangled ? mangled.value()
+ : reference.name.value());
+ } else if (reference.id) {
+ return symbols->findById(reference.id.value());
+ } else {
+ return nullptr;
+ }
}
const SymbolTable::Symbol* ReferenceLinker::resolveSymbolCheckVisibility(
- const Reference& reference, NameMangler* nameMangler, SymbolTable* symbols,
- CallSite* callSite, std::string* outError) {
- const SymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols);
- if (!symbol) {
- if (outError) *outError = "not found";
- return nullptr;
- }
+ const Reference& reference, NameMangler* nameMangler, SymbolTable* symbols,
+ CallSite* callSite, std::string* outError) {
+ const SymbolTable::Symbol* symbol =
+ resolveSymbol(reference, nameMangler, symbols);
+ if (!symbol) {
+ if (outError) *outError = "not found";
+ return nullptr;
+ }
- if (!isSymbolVisible(*symbol, reference, *callSite)) {
- if (outError) *outError = "is private";
- return nullptr;
- }
- return symbol;
+ if (!isSymbolVisible(*symbol, reference, *callSite)) {
+ if (outError) *outError = "is private";
+ return nullptr;
+ }
+ return symbol;
}
const SymbolTable::Symbol* ReferenceLinker::resolveAttributeCheckVisibility(
- const Reference& reference, NameMangler* nameMangler, SymbolTable* symbols,
- CallSite* callSite, std::string* outError) {
- const SymbolTable::Symbol* symbol = resolveSymbolCheckVisibility(reference, nameMangler,
- symbols, callSite,
- outError);
- if (!symbol) {
- return nullptr;
- }
+ const Reference& reference, NameMangler* nameMangler, SymbolTable* symbols,
+ CallSite* callSite, std::string* outError) {
+ const SymbolTable::Symbol* symbol = resolveSymbolCheckVisibility(
+ reference, nameMangler, symbols, callSite, outError);
+ if (!symbol) {
+ return nullptr;
+ }
- if (!symbol->attribute) {
- if (outError) *outError = "is not an attribute";
- return nullptr;
- }
- return symbol;
+ if (!symbol->attribute) {
+ if (outError) *outError = "is not an attribute";
+ return nullptr;
+ }
+ return symbol;
}
-Maybe<xml::AaptAttribute> ReferenceLinker::compileXmlAttribute(const Reference& reference,
- NameMangler* nameMangler,
- SymbolTable* symbols,
- CallSite* callSite,
- std::string* outError) {
- const SymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols);
- if (!symbol) {
- if (outError) *outError = "not found";
- return {};
- }
+Maybe<xml::AaptAttribute> ReferenceLinker::compileXmlAttribute(
+ const Reference& reference, NameMangler* nameMangler, SymbolTable* symbols,
+ CallSite* callSite, std::string* outError) {
+ const SymbolTable::Symbol* symbol =
+ resolveSymbol(reference, nameMangler, symbols);
+ if (!symbol) {
+ if (outError) *outError = "not found";
+ return {};
+ }
- if (!symbol->attribute) {
- if (outError) *outError = "is not an attribute";
- return {};
- }
- return xml::AaptAttribute{ symbol->id, *symbol->attribute };
+ if (!symbol->attribute) {
+ if (outError) *outError = "is not an attribute";
+ return {};
+ }
+ return xml::AaptAttribute{symbol->id, *symbol->attribute};
}
-void ReferenceLinker::writeResourceName(DiagMessage* outMsg, const Reference& orig,
+void ReferenceLinker::writeResourceName(DiagMessage* outMsg,
+ const Reference& orig,
const Reference& transformed) {
- assert(outMsg);
+ assert(outMsg);
- if (orig.name) {
- *outMsg << orig.name.value();
- if (transformed.name.value() != orig.name.value()) {
- *outMsg << " (aka " << transformed.name.value() << ")";
- }
- } else {
- *outMsg << orig.id.value();
+ if (orig.name) {
+ *outMsg << orig.name.value();
+ if (transformed.name.value() != orig.name.value()) {
+ *outMsg << " (aka " << transformed.name.value() << ")";
}
+ } else {
+ *outMsg << orig.id.value();
+ }
}
bool ReferenceLinker::linkReference(Reference* reference, IAaptContext* context,
- SymbolTable* symbols, xml::IPackageDeclStack* decls,
+ SymbolTable* symbols,
+ xml::IPackageDeclStack* decls,
CallSite* callSite) {
- assert(reference);
- assert(reference->name || reference->id);
+ assert(reference);
+ assert(reference->name || reference->id);
- Reference transformedReference = *reference;
- transformReferenceFromNamespace(decls, context->getCompilationPackage(),
- &transformedReference);
+ Reference transformedReference = *reference;
+ transformReferenceFromNamespace(decls, context->getCompilationPackage(),
+ &transformedReference);
- std::string errStr;
- const SymbolTable::Symbol* s = resolveSymbolCheckVisibility(
- transformedReference, context->getNameMangler(), symbols, callSite, &errStr);
- if (s) {
- // The ID may not exist. This is fine because of the possibility of building against
- // libraries without assigned IDs.
- // Ex: Linking against own resources when building a static library.
- reference->id = s->id;
- return true;
- }
+ std::string errStr;
+ const SymbolTable::Symbol* s = resolveSymbolCheckVisibility(
+ transformedReference, context->getNameMangler(), symbols, callSite,
+ &errStr);
+ if (s) {
+ // The ID may not exist. This is fine because of the possibility of building
+ // against
+ // libraries without assigned IDs.
+ // Ex: Linking against own resources when building a static library.
+ reference->id = s->id;
+ return true;
+ }
- DiagMessage errorMsg(reference->getSource());
- errorMsg << "resource ";
- writeResourceName(&errorMsg, *reference, transformedReference);
- errorMsg << " " << errStr;
- context->getDiagnostics()->error(errorMsg);
- return false;
+ DiagMessage errorMsg(reference->getSource());
+ errorMsg << "resource ";
+ writeResourceName(&errorMsg, *reference, transformedReference);
+ errorMsg << " " << errStr;
+ context->getDiagnostics()->error(errorMsg);
+ return false;
}
namespace {
struct EmptyDeclStack : public xml::IPackageDeclStack {
- Maybe<xml::ExtractedPackage> transformPackageAlias(
- const StringPiece& alias, const StringPiece& localPackage) const override {
- if (alias.empty()) {
- return xml::ExtractedPackage{ localPackage.toString(), true /* private */ };
- }
- return {};
+ Maybe<xml::ExtractedPackage> transformPackageAlias(
+ const StringPiece& alias,
+ const StringPiece& localPackage) const override {
+ if (alias.empty()) {
+ return xml::ExtractedPackage{localPackage.toString(), true /* private */};
}
+ return {};
+ }
};
-} // namespace
+} // namespace
bool ReferenceLinker::consume(IAaptContext* context, ResourceTable* table) {
- EmptyDeclStack declStack;
- bool error = false;
- for (auto& package : table->packages) {
- for (auto& type : package->types) {
- for (auto& entry : type->entries) {
- // Symbol state information may be lost if there is no value for the resource.
- if (entry->symbolStatus.state != SymbolState::kUndefined && entry->values.empty()) {
- context->getDiagnostics()->error(
- DiagMessage(entry->symbolStatus.source)
- << "no definition for declared symbol '"
- << ResourceNameRef(package->name, type->type, entry->name)
- << "'");
- error = true;
- }
-
- CallSite callSite = { ResourceNameRef(package->name, type->type, entry->name) };
- ReferenceLinkerVisitor visitor(context, context->getExternalSymbols(),
- &table->stringPool, &declStack, &callSite);
-
- for (auto& configValue : entry->values) {
- configValue->value->accept(&visitor);
- }
-
- if (visitor.hasError()) {
- error = true;
- }
- }
+ EmptyDeclStack declStack;
+ bool error = false;
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ // Symbol state information may be lost if there is no value for the
+ // resource.
+ if (entry->symbolStatus.state != SymbolState::kUndefined &&
+ entry->values.empty()) {
+ context->getDiagnostics()->error(
+ DiagMessage(entry->symbolStatus.source)
+ << "no definition for declared symbol '"
+ << ResourceNameRef(package->name, type->type, entry->name)
+ << "'");
+ error = true;
}
+
+ CallSite callSite = {
+ ResourceNameRef(package->name, type->type, entry->name)};
+ ReferenceLinkerVisitor visitor(context, context->getExternalSymbols(),
+ &table->stringPool, &declStack,
+ &callSite);
+
+ for (auto& configValue : entry->values) {
+ configValue->value->accept(&visitor);
+ }
+
+ if (visitor.hasError()) {
+ error = true;
+ }
+ }
}
- return !error;
+ }
+ return !error;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h
index 7993aaf..8f6604f 100644
--- a/tools/aapt2/link/ReferenceLinker.h
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -30,77 +30,86 @@
namespace aapt {
/**
- * Resolves all references to resources in the ResourceTable and assigns them IDs.
+ * Resolves all references to resources in the ResourceTable and assigns them
+ * IDs.
* The ResourceTable must already have IDs assigned to each resource.
- * Once the ResourceTable is processed by this linker, it is ready to be flattened.
+ * Once the ResourceTable is processed by this linker, it is ready to be
+ * flattened.
*/
struct ReferenceLinker : public IResourceTableConsumer {
- /**
- * Returns true if the symbol is visible by the reference and from the callsite.
- */
- static bool isSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref,
- const CallSite& callSite);
+ /**
+ * Returns true if the symbol is visible by the reference and from the
+ * callsite.
+ */
+ static bool isSymbolVisible(const SymbolTable::Symbol& symbol,
+ const Reference& ref, const CallSite& callSite);
- /**
- * Performs name mangling and looks up the resource in the symbol table. Returns nullptr
- * if the symbol was not found.
- */
- static const SymbolTable::Symbol* resolveSymbol(const Reference& reference,
- NameMangler* mangler, SymbolTable* symbols);
+ /**
+ * Performs name mangling and looks up the resource in the symbol table.
+ * Returns nullptr
+ * if the symbol was not found.
+ */
+ static const SymbolTable::Symbol* resolveSymbol(const Reference& reference,
+ NameMangler* mangler,
+ SymbolTable* symbols);
- /**
- * Performs name mangling and looks up the resource in the symbol table. If the symbol is
- * not visible by the reference at the callsite, nullptr is returned. outError holds
- * the error message.
- */
- static const SymbolTable::Symbol* resolveSymbolCheckVisibility(const Reference& reference,
- NameMangler* nameMangler,
- SymbolTable* symbols,
- CallSite* callSite,
- std::string* outError);
+ /**
+ * Performs name mangling and looks up the resource in the symbol table. If
+ * the symbol is
+ * not visible by the reference at the callsite, nullptr is returned. outError
+ * holds
+ * the error message.
+ */
+ static const SymbolTable::Symbol* resolveSymbolCheckVisibility(
+ const Reference& reference, NameMangler* nameMangler,
+ SymbolTable* symbols, CallSite* callSite, std::string* outError);
- /**
- * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is an attribute.
- * That is, the return value will have a non-null value for ISymbolTable::Symbol::attribute.
- */
- static const SymbolTable::Symbol* resolveAttributeCheckVisibility(const Reference& reference,
- NameMangler* nameMangler,
- SymbolTable* symbols,
- CallSite* callSite,
- std::string* outError);
+ /**
+ * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is
+ * an attribute.
+ * That is, the return value will have a non-null value for
+ * ISymbolTable::Symbol::attribute.
+ */
+ static const SymbolTable::Symbol* resolveAttributeCheckVisibility(
+ const Reference& reference, NameMangler* nameMangler,
+ SymbolTable* symbols, CallSite* callSite, std::string* outError);
- /**
- * Resolves the attribute reference and returns an xml::AaptAttribute if successful.
- * If resolution fails, outError holds the error message.
- */
- static Maybe<xml::AaptAttribute> compileXmlAttribute(const Reference& reference,
- NameMangler* nameMangler,
- SymbolTable* symbols,
- CallSite* callSite,
- std::string* outError);
+ /**
+ * Resolves the attribute reference and returns an xml::AaptAttribute if
+ * successful.
+ * If resolution fails, outError holds the error message.
+ */
+ static Maybe<xml::AaptAttribute> compileXmlAttribute(
+ const Reference& reference, NameMangler* nameMangler,
+ SymbolTable* symbols, CallSite* callSite, std::string* outError);
- /**
- * Writes the resource name to the DiagMessage, using the "orig_name (aka <transformed_name>)"
- * syntax.
- */
- static void writeResourceName(DiagMessage* outMsg, const Reference& orig,
- const Reference& transformed);
+ /**
+ * Writes the resource name to the DiagMessage, using the "orig_name (aka
+ * <transformed_name>)"
+ * syntax.
+ */
+ static void writeResourceName(DiagMessage* outMsg, const Reference& orig,
+ const Reference& transformed);
- /**
- * Transforms the package name of the reference to the fully qualified package name using
- * the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible
- * to the reference at the callsite, the reference is updated with an ID.
- * Returns false on failure, and an error message is logged to the IDiagnostics in the context.
- */
- static bool linkReference(Reference* reference, IAaptContext* context, SymbolTable* symbols,
- xml::IPackageDeclStack* decls, CallSite* callSite);
+ /**
+ * Transforms the package name of the reference to the fully qualified package
+ * name using
+ * the xml::IPackageDeclStack, then mangles and looks up the symbol. If the
+ * symbol is visible
+ * to the reference at the callsite, the reference is updated with an ID.
+ * Returns false on failure, and an error message is logged to the
+ * IDiagnostics in the context.
+ */
+ static bool linkReference(Reference* reference, IAaptContext* context,
+ SymbolTable* symbols, xml::IPackageDeclStack* decls,
+ CallSite* callSite);
- /**
- * Links all references in the ResourceTable.
- */
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ /**
+ * Links all references in the ResourceTable.
+ */
+ bool consume(IAaptContext* context, ResourceTable* table) override;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_LINKER_REFERENCELINKER_H */
diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp
index 5c1511f..8aa3616 100644
--- a/tools/aapt2/link/ReferenceLinker_test.cpp
+++ b/tools/aapt2/link/ReferenceLinker_test.cpp
@@ -22,206 +22,238 @@
namespace aapt {
TEST(ReferenceLinkerTest, LinkSimpleReferences) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("com.app.test", 0x7f)
- .addReference("com.app.test:string/foo", ResourceId(0x7f020000),
- "com.app.test:string/bar")
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .setPackageId("com.app.test", 0x7f)
+ .addReference("com.app.test:string/foo", ResourceId(0x7f020000),
+ "com.app.test:string/bar")
- // Test use of local reference (w/o package name).
- .addReference("com.app.test:string/bar", ResourceId(0x7f020001), "string/baz")
+ // Test use of local reference (w/o package name).
+ .addReference("com.app.test:string/bar", ResourceId(0x7f020001),
+ "string/baz")
- .addReference("com.app.test:string/baz", ResourceId(0x7f020002),
- "android:string/ok")
- .build();
+ .addReference("com.app.test:string/baz", ResourceId(0x7f020002),
+ "android:string/ok")
+ .build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .setCompilationPackage("com.app.test")
- .setPackageId(0x7f)
- .setNameManglerPolicy(NameManglerPolicy{ "com.app.test" })
- .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
- .addSymbolSource(test::StaticSymbolSourceBuilder()
- .addPublicSymbol("android:string/ok", ResourceId(0x01040034))
- .build())
- .build();
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .setCompilationPackage("com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{"com.app.test"})
+ .addSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .addSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .addPublicSymbol("android:string/ok", ResourceId(0x01040034))
+ .build())
+ .build();
- ReferenceLinker linker;
- ASSERT_TRUE(linker.consume(context.get(), table.get()));
+ ReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(context.get(), table.get()));
- Reference* ref = test::getValue<Reference>(table.get(), "com.app.test:string/foo");
- ASSERT_NE(ref, nullptr);
- AAPT_ASSERT_TRUE(ref->id);
- EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001));
+ Reference* ref =
+ test::getValue<Reference>(table.get(), "com.app.test:string/foo");
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001));
- ref = test::getValue<Reference>(table.get(), "com.app.test:string/bar");
- ASSERT_NE(ref, nullptr);
- AAPT_ASSERT_TRUE(ref->id);
- EXPECT_EQ(ref->id.value(), ResourceId(0x7f020002));
+ ref = test::getValue<Reference>(table.get(), "com.app.test:string/bar");
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x7f020002));
- ref = test::getValue<Reference>(table.get(), "com.app.test:string/baz");
- ASSERT_NE(ref, nullptr);
- AAPT_ASSERT_TRUE(ref->id);
- EXPECT_EQ(ref->id.value(), ResourceId(0x01040034));
+ ref = test::getValue<Reference>(table.get(), "com.app.test:string/baz");
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x01040034));
}
TEST(ReferenceLinkerTest, LinkStyleAttributes) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("com.app.test", 0x7f)
- .addValue("com.app.test:style/Theme", test::StyleBuilder()
- .setParent("android:style/Theme.Material")
- .addItem("android:attr/foo", ResourceUtils::tryParseColor("#ff00ff"))
- .addItem("android:attr/bar", {} /* placeholder */)
- .build())
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .setPackageId("com.app.test", 0x7f)
+ .addValue("com.app.test:style/Theme",
+ test::StyleBuilder()
+ .setParent("android:style/Theme.Material")
+ .addItem("android:attr/foo",
+ ResourceUtils::tryParseColor("#ff00ff"))
+ .addItem("android:attr/bar", {} /* placeholder */)
+ .build())
+ .build();
- {
- // We need to fill in the value for the attribute android:attr/bar after we build the
- // table, because we need access to the string pool.
- Style* style = test::getValue<Style>(table.get(), "com.app.test:style/Theme");
- ASSERT_NE(style, nullptr);
- style->entries.back().value = util::make_unique<RawString>(
- table->stringPool.makeRef("one|two"));
- }
-
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .setCompilationPackage("com.app.test")
- .setPackageId(0x7f)
- .setNameManglerPolicy(NameManglerPolicy{ "com.app.test" })
- .addSymbolSource(test::StaticSymbolSourceBuilder()
- .addPublicSymbol("android:style/Theme.Material",
- ResourceId(0x01060000))
- .addPublicSymbol("android:attr/foo", ResourceId(0x01010001),
- test::AttributeBuilder()
- .setTypeMask(ResTable_map::TYPE_COLOR)
- .build())
- .addPublicSymbol("android:attr/bar", ResourceId(0x01010002),
- test::AttributeBuilder()
- .setTypeMask(ResTable_map::TYPE_FLAGS)
- .addItem("one", 0x01)
- .addItem("two", 0x02)
- .build())
- .build())
- .build();
-
- ReferenceLinker linker;
- ASSERT_TRUE(linker.consume(context.get(), table.get()));
-
- Style* style = test::getValue<Style>(table.get(), "com.app.test:style/Theme");
+ {
+ // We need to fill in the value for the attribute android:attr/bar after we
+ // build the
+ // table, because we need access to the string pool.
+ Style* style =
+ test::getValue<Style>(table.get(), "com.app.test:style/Theme");
ASSERT_NE(style, nullptr);
- AAPT_ASSERT_TRUE(style->parent);
- AAPT_ASSERT_TRUE(style->parent.value().id);
- EXPECT_EQ(style->parent.value().id.value(), ResourceId(0x01060000));
+ style->entries.back().value =
+ util::make_unique<RawString>(table->stringPool.makeRef("one|two"));
+ }
- ASSERT_EQ(2u, style->entries.size());
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .setCompilationPackage("com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{"com.app.test"})
+ .addSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .addPublicSymbol("android:style/Theme.Material",
+ ResourceId(0x01060000))
+ .addPublicSymbol("android:attr/foo", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .setTypeMask(ResTable_map::TYPE_COLOR)
+ .build())
+ .addPublicSymbol("android:attr/bar", ResourceId(0x01010002),
+ test::AttributeBuilder()
+ .setTypeMask(ResTable_map::TYPE_FLAGS)
+ .addItem("one", 0x01)
+ .addItem("two", 0x02)
+ .build())
+ .build())
+ .build();
- AAPT_ASSERT_TRUE(style->entries[0].key.id);
- EXPECT_EQ(style->entries[0].key.id.value(), ResourceId(0x01010001));
- ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[0].value.get()), nullptr);
+ ReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(context.get(), table.get()));
- AAPT_ASSERT_TRUE(style->entries[1].key.id);
- EXPECT_EQ(style->entries[1].key.id.value(), ResourceId(0x01010002));
- ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[1].value.get()), nullptr);
+ Style* style = test::getValue<Style>(table.get(), "com.app.test:style/Theme");
+ ASSERT_NE(style, nullptr);
+ AAPT_ASSERT_TRUE(style->parent);
+ AAPT_ASSERT_TRUE(style->parent.value().id);
+ EXPECT_EQ(style->parent.value().id.value(), ResourceId(0x01060000));
+
+ ASSERT_EQ(2u, style->entries.size());
+
+ AAPT_ASSERT_TRUE(style->entries[0].key.id);
+ EXPECT_EQ(style->entries[0].key.id.value(), ResourceId(0x01010001));
+ ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[0].value.get()), nullptr);
+
+ AAPT_ASSERT_TRUE(style->entries[1].key.id);
+ EXPECT_EQ(style->entries[1].key.id.value(), ResourceId(0x01010002));
+ ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[1].value.get()), nullptr);
}
TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .setCompilationPackage("com.app.test")
- .setPackageId(0x7f)
- .setNameManglerPolicy(NameManglerPolicy{ "com.app.test", { "com.android.support" } })
- .addSymbolSource(test::StaticSymbolSourceBuilder()
- .addPublicSymbol("com.app.test:attr/com.android.support$foo",
- ResourceId(0x7f010000),
- test::AttributeBuilder()
- .setTypeMask(ResTable_map::TYPE_COLOR)
- .build())
- .build())
- .build();
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .setCompilationPackage("com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(
+ NameManglerPolicy{"com.app.test", {"com.android.support"}})
+ .addSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .addPublicSymbol("com.app.test:attr/com.android.support$foo",
+ ResourceId(0x7f010000),
+ test::AttributeBuilder()
+ .setTypeMask(ResTable_map::TYPE_COLOR)
+ .build())
+ .build())
+ .build();
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("com.app.test", 0x7f)
- .addValue("com.app.test:style/Theme", ResourceId(0x7f020000),
- test::StyleBuilder().addItem("com.android.support:attr/foo",
- ResourceUtils::tryParseColor("#ff0000"))
- .build())
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .setPackageId("com.app.test", 0x7f)
+ .addValue("com.app.test:style/Theme", ResourceId(0x7f020000),
+ test::StyleBuilder()
+ .addItem("com.android.support:attr/foo",
+ ResourceUtils::tryParseColor("#ff0000"))
+ .build())
+ .build();
- ReferenceLinker linker;
- ASSERT_TRUE(linker.consume(context.get(), table.get()));
+ ReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(context.get(), table.get()));
- Style* style = test::getValue<Style>(table.get(), "com.app.test:style/Theme");
- ASSERT_NE(style, nullptr);
- ASSERT_EQ(1u, style->entries.size());
- AAPT_ASSERT_TRUE(style->entries.front().key.id);
- EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000));
+ Style* style = test::getValue<Style>(table.get(), "com.app.test:style/Theme");
+ ASSERT_NE(style, nullptr);
+ ASSERT_EQ(1u, style->entries.size());
+ AAPT_ASSERT_TRUE(style->entries.front().key.id);
+ EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000));
}
TEST(ReferenceLinkerTest, FailToLinkPrivateSymbols) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("com.app.test", 0x7f)
- .addReference("com.app.test:string/foo", ResourceId(0x7f020000),
- "android:string/hidden")
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .setPackageId("com.app.test", 0x7f)
+ .addReference("com.app.test:string/foo", ResourceId(0x7f020000),
+ "android:string/hidden")
+ .build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .setCompilationPackage("com.app.test")
- .setPackageId(0x7f)
- .setNameManglerPolicy(NameManglerPolicy{ "com.app.test" })
- .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
- .addSymbolSource(test::StaticSymbolSourceBuilder()
- .addSymbol("android:string/hidden", ResourceId(0x01040034))
- .build())
- .build();
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .setCompilationPackage("com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{"com.app.test"})
+ .addSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .addSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .addSymbol("android:string/hidden", ResourceId(0x01040034))
+ .build())
+ .build();
- ReferenceLinker linker;
- ASSERT_FALSE(linker.consume(context.get(), table.get()));
+ ReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(context.get(), table.get()));
}
TEST(ReferenceLinkerTest, FailToLinkPrivateMangledSymbols) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("com.app.test", 0x7f)
- .addReference("com.app.test:string/foo", ResourceId(0x7f020000),
- "com.app.lib:string/hidden")
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .setPackageId("com.app.test", 0x7f)
+ .addReference("com.app.test:string/foo", ResourceId(0x7f020000),
+ "com.app.lib:string/hidden")
+ .build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .setCompilationPackage("com.app.test")
- .setPackageId(0x7f)
- .setNameManglerPolicy(NameManglerPolicy{ "com.app.test", { "com.app.lib" } })
- .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
- .addSymbolSource(test::StaticSymbolSourceBuilder()
- .addSymbol("com.app.test:string/com.app.lib$hidden",
- ResourceId(0x7f040034))
- .build())
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .setCompilationPackage("com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(
+ NameManglerPolicy{"com.app.test", {"com.app.lib"}})
+ .addSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .addSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .addSymbol("com.app.test:string/com.app.lib$hidden",
+ ResourceId(0x7f040034))
+ .build())
- .build();
+ .build();
- ReferenceLinker linker;
- ASSERT_FALSE(linker.consume(context.get(), table.get()));
+ ReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(context.get(), table.get()));
}
TEST(ReferenceLinkerTest, FailToLinkPrivateStyleAttributes) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("com.app.test", 0x7f)
- .addValue("com.app.test:style/Theme", test::StyleBuilder()
- .addItem("android:attr/hidden", ResourceUtils::tryParseColor("#ff00ff"))
- .build())
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .setPackageId("com.app.test", 0x7f)
+ .addValue("com.app.test:style/Theme",
+ test::StyleBuilder()
+ .addItem("android:attr/hidden",
+ ResourceUtils::tryParseColor("#ff00ff"))
+ .build())
+ .build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .setCompilationPackage("com.app.test")
- .setPackageId(0x7f)
- .setNameManglerPolicy(NameManglerPolicy{ "com.app.test" })
- .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
- .addSymbolSource(test::StaticSymbolSourceBuilder()
- .addSymbol("android:attr/hidden", ResourceId(0x01010001),
- test::AttributeBuilder()
- .setTypeMask(
- android::ResTable_map::TYPE_COLOR)
- .build())
- .build())
- .build();
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .setCompilationPackage("com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{"com.app.test"})
+ .addSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .addSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .addSymbol("android:attr/hidden", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_COLOR)
+ .build())
+ .build())
+ .build();
- ReferenceLinker linker;
- ASSERT_FALSE(linker.consume(context.get(), table.get()));
+ ReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(context.get(), table.get()));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/ResourceDeduper.cpp b/tools/aapt2/link/ResourceDeduper.cpp
index 0276261..f565359 100644
--- a/tools/aapt2/link/ResourceDeduper.cpp
+++ b/tools/aapt2/link/ResourceDeduper.cpp
@@ -36,79 +36,81 @@
* an equivalent entry value.
*/
class DominatedKeyValueRemover : public DominatorTree::BottomUpVisitor {
-public:
- using Node = DominatorTree::Node;
+ public:
+ using Node = DominatorTree::Node;
- explicit DominatedKeyValueRemover(IAaptContext* context, ResourceEntry* entry) :
- mContext(context), mEntry(entry) {
+ explicit DominatedKeyValueRemover(IAaptContext* context, ResourceEntry* entry)
+ : mContext(context), mEntry(entry) {}
+
+ void visitConfig(Node* node) {
+ Node* parent = node->parent();
+ if (!parent) {
+ return;
+ }
+ ResourceConfigValue* nodeValue = node->value();
+ ResourceConfigValue* parentValue = parent->value();
+ if (!nodeValue || !parentValue) {
+ return;
+ }
+ if (!nodeValue->value->equals(parentValue->value.get())) {
+ return;
}
- void visitConfig(Node* node) {
- Node* parent = node->parent();
- if (!parent) {
- return;
- }
- ResourceConfigValue* nodeValue = node->value();
- ResourceConfigValue* parentValue = parent->value();
- if (!nodeValue || !parentValue) {
- return;
- }
- if (!nodeValue->value->equals(parentValue->value.get())) {
- return;
- }
-
- // Compare compatible configs for this entry and ensure the values are
- // equivalent.
- const ConfigDescription& nodeConfiguration = nodeValue->config;
- for (const auto& sibling : mEntry->values) {
- if (!sibling->value) {
- // Sibling was already removed.
- continue;
- }
- if (nodeConfiguration.isCompatibleWith(sibling->config)
- && !nodeValue->value->equals(sibling->value.get())) {
- // The configurations are compatible, but the value is
- // different, so we can't remove this value.
- return;
- }
- }
- if (mContext->verbose()) {
- mContext->getDiagnostics()->note(
- DiagMessage(nodeValue->value->getSource())
- << "removing dominated duplicate resource with name \""
- << mEntry->name << "\"");
- }
- nodeValue->value = {};
+ // Compare compatible configs for this entry and ensure the values are
+ // equivalent.
+ const ConfigDescription& nodeConfiguration = nodeValue->config;
+ for (const auto& sibling : mEntry->values) {
+ if (!sibling->value) {
+ // Sibling was already removed.
+ continue;
+ }
+ if (nodeConfiguration.isCompatibleWith(sibling->config) &&
+ !nodeValue->value->equals(sibling->value.get())) {
+ // The configurations are compatible, but the value is
+ // different, so we can't remove this value.
+ return;
+ }
}
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(
+ DiagMessage(nodeValue->value->getSource())
+ << "removing dominated duplicate resource with name \""
+ << mEntry->name << "\"");
+ }
+ nodeValue->value = {};
+ }
-private:
- IAaptContext* mContext;
- ResourceEntry* mEntry;
+ private:
+ IAaptContext* mContext;
+ ResourceEntry* mEntry;
};
static void dedupeEntry(IAaptContext* context, ResourceEntry* entry) {
- DominatorTree tree(entry->values);
- DominatedKeyValueRemover remover(context, entry);
- tree.accept(&remover);
+ DominatorTree tree(entry->values);
+ DominatedKeyValueRemover remover(context, entry);
+ tree.accept(&remover);
- // Erase the values that were removed.
- entry->values.erase(std::remove_if(entry->values.begin(), entry->values.end(),
- [](const std::unique_ptr<ResourceConfigValue>& val) -> bool {
- return val == nullptr || val->value == nullptr;
- }), entry->values.end());
+ // Erase the values that were removed.
+ entry->values.erase(
+ std::remove_if(
+ entry->values.begin(), entry->values.end(),
+ [](const std::unique_ptr<ResourceConfigValue>& val) -> bool {
+ return val == nullptr || val->value == nullptr;
+ }),
+ entry->values.end());
}
-} // namespace
+} // namespace
bool ResourceDeduper::consume(IAaptContext* context, ResourceTable* table) {
- for (auto& package : table->packages) {
- for (auto& type : package->types) {
- for (auto& entry : type->entries) {
- dedupeEntry(context, entry.get());
- }
- }
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ dedupeEntry(context, entry.get());
+ }
}
- return true;
+ }
+ return true;
}
-} // aapt
+} // aapt
diff --git a/tools/aapt2/link/ResourceDeduper_test.cpp b/tools/aapt2/link/ResourceDeduper_test.cpp
index 47071a51..7e2d476 100644
--- a/tools/aapt2/link/ResourceDeduper_test.cpp
+++ b/tools/aapt2/link/ResourceDeduper_test.cpp
@@ -21,63 +21,63 @@
namespace aapt {
TEST(ResourceDeduperTest, SameValuesAreDeduped) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- const ConfigDescription defaultConfig = {};
- const ConfigDescription enConfig = test::parseConfigOrDie("en");
- const ConfigDescription enV21Config = test::parseConfigOrDie("en-v21");
- // Chosen because this configuration is compatible with en.
- const ConfigDescription landConfig = test::parseConfigOrDie("land");
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ const ConfigDescription defaultConfig = {};
+ const ConfigDescription enConfig = test::parseConfigOrDie("en");
+ const ConfigDescription enV21Config = test::parseConfigOrDie("en-v21");
+ // Chosen because this configuration is compatible with en.
+ const ConfigDescription landConfig = test::parseConfigOrDie("land");
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addString("android:string/dedupe", ResourceId{}, defaultConfig, "dedupe")
- .addString("android:string/dedupe", ResourceId{}, enConfig, "dedupe")
- .addString("android:string/dedupe", ResourceId{}, landConfig, "dedupe")
- .addString("android:string/dedupe2", ResourceId{}, defaultConfig, "dedupe")
- .addString("android:string/dedupe2", ResourceId{}, enConfig, "dedupe")
- .addString("android:string/dedupe2", ResourceId{}, enV21Config, "keep")
- .addString("android:string/dedupe2", ResourceId{}, landConfig, "dedupe")
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .addString("android:string/dedupe", ResourceId{}, defaultConfig,
+ "dedupe")
+ .addString("android:string/dedupe", ResourceId{}, enConfig, "dedupe")
+ .addString("android:string/dedupe", ResourceId{}, landConfig,
+ "dedupe")
+ .addString("android:string/dedupe2", ResourceId{}, defaultConfig,
+ "dedupe")
+ .addString("android:string/dedupe2", ResourceId{}, enConfig, "dedupe")
+ .addString("android:string/dedupe2", ResourceId{}, enV21Config,
+ "keep")
+ .addString("android:string/dedupe2", ResourceId{}, landConfig,
+ "dedupe")
+ .build();
- ASSERT_TRUE(ResourceDeduper().consume(context.get(), table.get()));
- EXPECT_EQ(
- nullptr,
- test::getValueForConfig<String>(table.get(), "android:string/dedupe", enConfig));
- EXPECT_EQ(
- nullptr,
- test::getValueForConfig<String>(table.get(), "android:string/dedupe", landConfig));
- EXPECT_EQ(
- nullptr,
- test::getValueForConfig<String>(table.get(), "android:string/dedupe2", enConfig));
- EXPECT_NE(
- nullptr,
- test::getValueForConfig<String>(table.get(), "android:string/dedupe2", enV21Config));
+ ASSERT_TRUE(ResourceDeduper().consume(context.get(), table.get()));
+ EXPECT_EQ(nullptr, test::getValueForConfig<String>(
+ table.get(), "android:string/dedupe", enConfig));
+ EXPECT_EQ(nullptr, test::getValueForConfig<String>(
+ table.get(), "android:string/dedupe", landConfig));
+ EXPECT_EQ(nullptr, test::getValueForConfig<String>(
+ table.get(), "android:string/dedupe2", enConfig));
+ EXPECT_NE(nullptr, test::getValueForConfig<String>(
+ table.get(), "android:string/dedupe2", enV21Config));
}
TEST(ResourceDeduperTest, DifferentValuesAreKept) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- const ConfigDescription defaultConfig = {};
- const ConfigDescription enConfig = test::parseConfigOrDie("en");
- const ConfigDescription enV21Config = test::parseConfigOrDie("en-v21");
- // Chosen because this configuration is compatible with en.
- const ConfigDescription landConfig = test::parseConfigOrDie("land");
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ const ConfigDescription defaultConfig = {};
+ const ConfigDescription enConfig = test::parseConfigOrDie("en");
+ const ConfigDescription enV21Config = test::parseConfigOrDie("en-v21");
+ // Chosen because this configuration is compatible with en.
+ const ConfigDescription landConfig = test::parseConfigOrDie("land");
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addString("android:string/keep", ResourceId{}, defaultConfig, "keep")
- .addString("android:string/keep", ResourceId{}, enConfig, "keep")
- .addString("android:string/keep", ResourceId{}, enV21Config, "keep2")
- .addString("android:string/keep", ResourceId{}, landConfig, "keep2")
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .addString("android:string/keep", ResourceId{}, defaultConfig, "keep")
+ .addString("android:string/keep", ResourceId{}, enConfig, "keep")
+ .addString("android:string/keep", ResourceId{}, enV21Config, "keep2")
+ .addString("android:string/keep", ResourceId{}, landConfig, "keep2")
+ .build();
- ASSERT_TRUE(ResourceDeduper().consume(context.get(), table.get()));
- EXPECT_NE(
- nullptr,
- test::getValueForConfig<String>(table.get(), "android:string/keep", enConfig));
- EXPECT_NE(
- nullptr,
- test::getValueForConfig<String>(table.get(), "android:string/keep", enV21Config));
- EXPECT_NE(
- nullptr,
- test::getValueForConfig<String>(table.get(), "android:string/keep", landConfig));
+ ASSERT_TRUE(ResourceDeduper().consume(context.get(), table.get()));
+ EXPECT_NE(nullptr, test::getValueForConfig<String>(
+ table.get(), "android:string/keep", enConfig));
+ EXPECT_NE(nullptr, test::getValueForConfig<String>(
+ table.get(), "android:string/keep", enV21Config));
+ EXPECT_NE(nullptr, test::getValueForConfig<String>(
+ table.get(), "android:string/keep", landConfig));
}
} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index eea4306..adf83a4 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -14,11 +14,11 @@
* limitations under the License.
*/
+#include "link/TableMerger.h"
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
-#include "link/TableMerger.h"
#include "util/Util.h"
#include <cassert>
@@ -26,348 +26,365 @@
namespace aapt {
TableMerger::TableMerger(IAaptContext* context, ResourceTable* outTable,
- const TableMergerOptions& options) :
- mContext(context), mMasterTable(outTable), mOptions(options) {
- // Create the desired package that all tables will be merged into.
- mMasterPackage = mMasterTable->createPackage(
- mContext->getCompilationPackage(), mContext->getPackageId());
- assert(mMasterPackage && "package name or ID already taken");
+ const TableMergerOptions& options)
+ : mContext(context), mMasterTable(outTable), mOptions(options) {
+ // Create the desired package that all tables will be merged into.
+ mMasterPackage = mMasterTable->createPackage(
+ mContext->getCompilationPackage(), mContext->getPackageId());
+ assert(mMasterPackage && "package name or ID already taken");
}
bool TableMerger::merge(const Source& src, ResourceTable* table,
io::IFileCollection* collection) {
- return mergeImpl(src, table, collection, false /* overlay */, true /* allow new */);
+ return mergeImpl(src, table, collection, false /* overlay */,
+ true /* allow new */);
}
bool TableMerger::mergeOverlay(const Source& src, ResourceTable* table,
io::IFileCollection* collection) {
- return mergeImpl(src, table, collection, true /* overlay */, mOptions.autoAddOverlay);
+ return mergeImpl(src, table, collection, true /* overlay */,
+ mOptions.autoAddOverlay);
}
/**
* This will merge packages with the same package name (or no package name).
*/
bool TableMerger::mergeImpl(const Source& src, ResourceTable* table,
- io::IFileCollection* collection,
- bool overlay, bool allowNew) {
- const uint8_t desiredPackageId = mContext->getPackageId();
+ io::IFileCollection* collection, bool overlay,
+ bool allowNew) {
+ const uint8_t desiredPackageId = mContext->getPackageId();
- bool error = false;
- for (auto& package : table->packages) {
- // Warn of packages with an unrelated ID.
- const Maybe<ResourceId>& id = package->id;
- if (id && id.value() != 0x0 && id.value() != desiredPackageId) {
- mContext->getDiagnostics()->warn(DiagMessage(src)
- << "ignoring package " << package->name);
- continue;
- }
-
- // Only merge an empty package or the package we're building.
- // Other packages may exist, which likely contain attribute definitions.
- // This is because at compile time it is unknown if the attributes are simply
- // uses of the attribute or definitions.
- if (package->name.empty() || mContext->getCompilationPackage() == package->name) {
- FileMergeCallback callback;
- if (collection) {
- callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
- FileReference* newFile, FileReference* oldFile) -> bool {
- // The old file's path points inside the APK, so we can use it as is.
- io::IFile* f = collection->findFile(*oldFile->path);
- if (!f) {
- mContext->getDiagnostics()->error(DiagMessage(src) << "file '"
- << *oldFile->path
- << "' not found");
- return false;
- }
-
- newFile->file = f;
- return true;
- };
- }
-
- // Merge here. Once the entries are merged and mangled, any references to
- // them are still valid. This is because un-mangled references are
- // mangled, then looked up at resolution time.
- // Also, when linking, we convert references with no package name to use
- // the compilation package name.
- error |= !doMerge(src, table, package.get(), false /* mangle */, overlay, allowNew,
- callback);
- }
+ bool error = false;
+ for (auto& package : table->packages) {
+ // Warn of packages with an unrelated ID.
+ const Maybe<ResourceId>& id = package->id;
+ if (id && id.value() != 0x0 && id.value() != desiredPackageId) {
+ mContext->getDiagnostics()->warn(DiagMessage(src) << "ignoring package "
+ << package->name);
+ continue;
}
- return !error;
+
+ // Only merge an empty package or the package we're building.
+ // Other packages may exist, which likely contain attribute definitions.
+ // This is because at compile time it is unknown if the attributes are
+ // simply
+ // uses of the attribute or definitions.
+ if (package->name.empty() ||
+ mContext->getCompilationPackage() == package->name) {
+ FileMergeCallback callback;
+ if (collection) {
+ callback = [&](const ResourceNameRef& name,
+ const ConfigDescription& config, FileReference* newFile,
+ FileReference* oldFile) -> bool {
+ // The old file's path points inside the APK, so we can use it as is.
+ io::IFile* f = collection->findFile(*oldFile->path);
+ if (!f) {
+ mContext->getDiagnostics()->error(DiagMessage(src)
+ << "file '" << *oldFile->path
+ << "' not found");
+ return false;
+ }
+
+ newFile->file = f;
+ return true;
+ };
+ }
+
+ // Merge here. Once the entries are merged and mangled, any references to
+ // them are still valid. This is because un-mangled references are
+ // mangled, then looked up at resolution time.
+ // Also, when linking, we convert references with no package name to use
+ // the compilation package name.
+ error |= !doMerge(src, table, package.get(), false /* mangle */, overlay,
+ allowNew, callback);
+ }
+ }
+ return !error;
}
/**
* This will merge and mangle resources from a static library.
*/
-bool TableMerger::mergeAndMangle(const Source& src, const StringPiece& packageName,
- ResourceTable* table, io::IFileCollection* collection) {
- bool error = false;
- for (auto& package : table->packages) {
- // Warn of packages with an unrelated ID.
- if (packageName != package->name) {
- mContext->getDiagnostics()->warn(DiagMessage(src)
- << "ignoring package " << package->name);
- continue;
- }
-
- bool mangle = packageName != mContext->getCompilationPackage();
- mMergedPackages.insert(package->name);
-
- auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
- FileReference* newFile, FileReference* oldFile) -> bool {
- // The old file's path points inside the APK, so we can use it as is.
- io::IFile* f = collection->findFile(*oldFile->path);
- if (!f) {
- mContext->getDiagnostics()->error(DiagMessage(src) << "file '" << *oldFile->path
- << "' not found");
- return false;
- }
-
- newFile->file = f;
- return true;
- };
-
- error |= !doMerge(src, table, package.get(),
- mangle, false /* overlay */, true /* allow new */, callback);
+bool TableMerger::mergeAndMangle(const Source& src,
+ const StringPiece& packageName,
+ ResourceTable* table,
+ io::IFileCollection* collection) {
+ bool error = false;
+ for (auto& package : table->packages) {
+ // Warn of packages with an unrelated ID.
+ if (packageName != package->name) {
+ mContext->getDiagnostics()->warn(DiagMessage(src) << "ignoring package "
+ << package->name);
+ continue;
}
- return !error;
+
+ bool mangle = packageName != mContext->getCompilationPackage();
+ mMergedPackages.insert(package->name);
+
+ auto callback = [&](const ResourceNameRef& name,
+ const ConfigDescription& config, FileReference* newFile,
+ FileReference* oldFile) -> bool {
+ // The old file's path points inside the APK, so we can use it as is.
+ io::IFile* f = collection->findFile(*oldFile->path);
+ if (!f) {
+ mContext->getDiagnostics()->error(
+ DiagMessage(src) << "file '" << *oldFile->path << "' not found");
+ return false;
+ }
+
+ newFile->file = f;
+ return true;
+ };
+
+ error |= !doMerge(src, table, package.get(), mangle, false /* overlay */,
+ true /* allow new */, callback);
+ }
+ return !error;
}
-static bool mergeType(IAaptContext* context, const Source& src, ResourceTableType* dstType,
- ResourceTableType* srcType) {
- if (dstType->symbolStatus.state < srcType->symbolStatus.state) {
- // The incoming type's visibility is stronger, so we should override
- // the visibility.
- if (srcType->symbolStatus.state == SymbolState::kPublic) {
- // Only copy the ID if the source is public, or else the ID is meaningless.
- dstType->id = srcType->id;
- }
- dstType->symbolStatus = std::move(srcType->symbolStatus);
- } else if (dstType->symbolStatus.state == SymbolState::kPublic
- && srcType->symbolStatus.state == SymbolState::kPublic
- && dstType->id && srcType->id
- && dstType->id.value() != srcType->id.value()) {
- // Both types are public and have different IDs.
- context->getDiagnostics()->error(DiagMessage(src)
- << "cannot merge type '" << srcType->type
- << "': conflicting public IDs");
- return false;
+static bool mergeType(IAaptContext* context, const Source& src,
+ ResourceTableType* dstType, ResourceTableType* srcType) {
+ if (dstType->symbolStatus.state < srcType->symbolStatus.state) {
+ // The incoming type's visibility is stronger, so we should override
+ // the visibility.
+ if (srcType->symbolStatus.state == SymbolState::kPublic) {
+ // Only copy the ID if the source is public, or else the ID is
+ // meaningless.
+ dstType->id = srcType->id;
}
- return true;
+ dstType->symbolStatus = std::move(srcType->symbolStatus);
+ } else if (dstType->symbolStatus.state == SymbolState::kPublic &&
+ srcType->symbolStatus.state == SymbolState::kPublic &&
+ dstType->id && srcType->id &&
+ dstType->id.value() != srcType->id.value()) {
+ // Both types are public and have different IDs.
+ context->getDiagnostics()->error(DiagMessage(src)
+ << "cannot merge type '" << srcType->type
+ << "': conflicting public IDs");
+ return false;
+ }
+ return true;
}
-static bool mergeEntry(IAaptContext* context, const Source& src, ResourceEntry* dstEntry,
- ResourceEntry* srcEntry) {
- if (dstEntry->symbolStatus.state < srcEntry->symbolStatus.state) {
- // The incoming type's visibility is stronger, so we should override
- // the visibility.
- if (srcEntry->symbolStatus.state == SymbolState::kPublic) {
- // Only copy the ID if the source is public, or else the ID is meaningless.
- dstEntry->id = srcEntry->id;
- }
- dstEntry->symbolStatus = std::move(srcEntry->symbolStatus);
- } else if (srcEntry->symbolStatus.state == SymbolState::kPublic
- && dstEntry->symbolStatus.state == SymbolState::kPublic
- && dstEntry->id && srcEntry->id
- && dstEntry->id.value() != srcEntry->id.value()) {
- // Both entries are public and have different IDs.
- context->getDiagnostics()->error(DiagMessage(src)
- << "cannot merge entry '" << srcEntry->name
- << "': conflicting public IDs");
- return false;
+static bool mergeEntry(IAaptContext* context, const Source& src,
+ ResourceEntry* dstEntry, ResourceEntry* srcEntry) {
+ if (dstEntry->symbolStatus.state < srcEntry->symbolStatus.state) {
+ // The incoming type's visibility is stronger, so we should override
+ // the visibility.
+ if (srcEntry->symbolStatus.state == SymbolState::kPublic) {
+ // Only copy the ID if the source is public, or else the ID is
+ // meaningless.
+ dstEntry->id = srcEntry->id;
}
- return true;
+ dstEntry->symbolStatus = std::move(srcEntry->symbolStatus);
+ } else if (srcEntry->symbolStatus.state == SymbolState::kPublic &&
+ dstEntry->symbolStatus.state == SymbolState::kPublic &&
+ dstEntry->id && srcEntry->id &&
+ dstEntry->id.value() != srcEntry->id.value()) {
+ // Both entries are public and have different IDs.
+ context->getDiagnostics()->error(DiagMessage(src)
+ << "cannot merge entry '" << srcEntry->name
+ << "': conflicting public IDs");
+ return false;
+ }
+ return true;
}
/**
* Modified CollisionResolver which will merge Styleables. Used with overlays.
*
* Styleables are not actual resources, but they are treated as such during the
- * compilation phase. Styleables don't simply overlay each other, their definitions merge
- * and accumulate. If both values are Styleables, we just merge them into the existing value.
+ * compilation phase. Styleables don't simply overlay each other, their
+ * definitions merge
+ * and accumulate. If both values are Styleables, we just merge them into the
+ * existing value.
*/
-static ResourceTable::CollisionResult resolveMergeCollision(Value* existing, Value* incoming) {
- if (Styleable* existingStyleable = valueCast<Styleable>(existing)) {
- if (Styleable* incomingStyleable = valueCast<Styleable>(incoming)) {
- // Styleables get merged.
- existingStyleable->mergeWith(incomingStyleable);
- return ResourceTable::CollisionResult::kKeepOriginal;
- }
+static ResourceTable::CollisionResult resolveMergeCollision(Value* existing,
+ Value* incoming) {
+ if (Styleable* existingStyleable = valueCast<Styleable>(existing)) {
+ if (Styleable* incomingStyleable = valueCast<Styleable>(incoming)) {
+ // Styleables get merged.
+ existingStyleable->mergeWith(incomingStyleable);
+ return ResourceTable::CollisionResult::kKeepOriginal;
}
- // Delegate to the default handler.
- return ResourceTable::resolveValueCollision(existing, incoming);
+ }
+ // Delegate to the default handler.
+ return ResourceTable::resolveValueCollision(existing, incoming);
}
-static ResourceTable::CollisionResult mergeConfigValue(IAaptContext* context,
- const ResourceNameRef& resName,
- const bool overlay,
- ResourceConfigValue* dstConfigValue,
- ResourceConfigValue* srcConfigValue) {
- using CollisionResult = ResourceTable::CollisionResult;
+static ResourceTable::CollisionResult mergeConfigValue(
+ IAaptContext* context, const ResourceNameRef& resName, const bool overlay,
+ ResourceConfigValue* dstConfigValue, ResourceConfigValue* srcConfigValue) {
+ using CollisionResult = ResourceTable::CollisionResult;
- Value* dstValue = dstConfigValue->value.get();
- Value* srcValue = srcConfigValue->value.get();
+ Value* dstValue = dstConfigValue->value.get();
+ Value* srcValue = srcConfigValue->value.get();
- CollisionResult collisionResult;
+ CollisionResult collisionResult;
+ if (overlay) {
+ collisionResult = resolveMergeCollision(dstValue, srcValue);
+ } else {
+ collisionResult = ResourceTable::resolveValueCollision(dstValue, srcValue);
+ }
+
+ if (collisionResult == CollisionResult::kConflict) {
if (overlay) {
- collisionResult = resolveMergeCollision(dstValue, srcValue);
- } else {
- collisionResult = ResourceTable::resolveValueCollision(dstValue, srcValue);
+ return CollisionResult::kTakeNew;
}
- if (collisionResult == CollisionResult::kConflict) {
- if (overlay) {
- return CollisionResult::kTakeNew;
- }
-
- // Error!
- context->getDiagnostics()->error(DiagMessage(srcValue->getSource())
- << "resource '" << resName
- << "' has a conflicting value for "
- << "configuration ("
- << srcConfigValue->config << ")");
- context->getDiagnostics()->note(DiagMessage(dstValue->getSource())
- << "originally defined here");
- return CollisionResult::kConflict;
- }
- return collisionResult;
+ // Error!
+ context->getDiagnostics()->error(
+ DiagMessage(srcValue->getSource())
+ << "resource '" << resName << "' has a conflicting value for "
+ << "configuration (" << srcConfigValue->config << ")");
+ context->getDiagnostics()->note(DiagMessage(dstValue->getSource())
+ << "originally defined here");
+ return CollisionResult::kConflict;
+ }
+ return collisionResult;
}
-bool TableMerger::doMerge(const Source& src,
- ResourceTable* srcTable,
+bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable,
ResourceTablePackage* srcPackage,
- const bool manglePackage,
- const bool overlay,
+ const bool manglePackage, const bool overlay,
const bool allowNewResources,
const FileMergeCallback& callback) {
- bool error = false;
+ bool error = false;
- for (auto& srcType : srcPackage->types) {
- ResourceTableType* dstType = mMasterPackage->findOrCreateType(srcType->type);
- if (!mergeType(mContext, src, dstType, srcType.get())) {
+ for (auto& srcType : srcPackage->types) {
+ ResourceTableType* dstType =
+ mMasterPackage->findOrCreateType(srcType->type);
+ if (!mergeType(mContext, src, dstType, srcType.get())) {
+ error = true;
+ continue;
+ }
+
+ for (auto& srcEntry : srcType->entries) {
+ std::string entryName = srcEntry->name;
+ if (manglePackage) {
+ entryName = NameMangler::mangleEntry(srcPackage->name, srcEntry->name);
+ }
+
+ ResourceEntry* dstEntry;
+ if (allowNewResources) {
+ dstEntry = dstType->findOrCreateEntry(entryName);
+ } else {
+ dstEntry = dstType->findEntry(entryName);
+ }
+
+ const ResourceNameRef resName(srcPackage->name, srcType->type,
+ srcEntry->name);
+
+ if (!dstEntry) {
+ mContext->getDiagnostics()->error(
+ DiagMessage(src) << "resource " << resName
+ << " does not override an existing resource");
+ mContext->getDiagnostics()->note(
+ DiagMessage(src) << "define an <add-resource> tag or use "
+ << "--auto-add-overlay");
+ error = true;
+ continue;
+ }
+
+ if (!mergeEntry(mContext, src, dstEntry, srcEntry.get())) {
+ error = true;
+ continue;
+ }
+
+ for (auto& srcConfigValue : srcEntry->values) {
+ using CollisionResult = ResourceTable::CollisionResult;
+
+ ResourceConfigValue* dstConfigValue = dstEntry->findValue(
+ srcConfigValue->config, srcConfigValue->product);
+ if (dstConfigValue) {
+ CollisionResult collisionResult = mergeConfigValue(
+ mContext, resName, overlay, dstConfigValue, srcConfigValue.get());
+ if (collisionResult == CollisionResult::kConflict) {
error = true;
continue;
+ } else if (collisionResult == CollisionResult::kKeepOriginal) {
+ continue;
+ }
+ } else {
+ dstConfigValue = dstEntry->findOrCreateValue(srcConfigValue->config,
+ srcConfigValue->product);
}
- for (auto& srcEntry : srcType->entries) {
- std::string entryName = srcEntry->name;
- if (manglePackage) {
- entryName = NameMangler::mangleEntry(srcPackage->name, srcEntry->name);
+ // Continue if we're taking the new resource.
+
+ if (FileReference* f =
+ valueCast<FileReference>(srcConfigValue->value.get())) {
+ std::unique_ptr<FileReference> newFileRef;
+ if (manglePackage) {
+ newFileRef = cloneAndMangleFile(srcPackage->name, *f);
+ } else {
+ newFileRef = std::unique_ptr<FileReference>(
+ f->clone(&mMasterTable->stringPool));
+ }
+
+ if (callback) {
+ if (!callback(resName, srcConfigValue->config, newFileRef.get(),
+ f)) {
+ error = true;
+ continue;
}
+ }
+ dstConfigValue->value = std::move(newFileRef);
- ResourceEntry* dstEntry;
- if (allowNewResources) {
- dstEntry = dstType->findOrCreateEntry(entryName);
- } else {
- dstEntry = dstType->findEntry(entryName);
- }
-
- const ResourceNameRef resName(srcPackage->name, srcType->type, srcEntry->name);
-
- if (!dstEntry) {
- mContext->getDiagnostics()->error(DiagMessage(src)
- << "resource " << resName
- << " does not override an existing resource");
- mContext->getDiagnostics()->note(DiagMessage(src)
- << "define an <add-resource> tag or use "
- << "--auto-add-overlay");
- error = true;
- continue;
- }
-
- if (!mergeEntry(mContext, src, dstEntry, srcEntry.get())) {
- error = true;
- continue;
- }
-
- for (auto& srcConfigValue : srcEntry->values) {
- using CollisionResult = ResourceTable::CollisionResult;
-
- ResourceConfigValue* dstConfigValue = dstEntry->findValue(srcConfigValue->config,
- srcConfigValue->product);
- if (dstConfigValue) {
- CollisionResult collisionResult = mergeConfigValue(
- mContext, resName, overlay, dstConfigValue, srcConfigValue.get());
- if (collisionResult == CollisionResult::kConflict) {
- error = true;
- continue;
- } else if (collisionResult == CollisionResult::kKeepOriginal) {
- continue;
- }
- } else {
- dstConfigValue = dstEntry->findOrCreateValue(srcConfigValue->config,
- srcConfigValue->product);
- }
-
- // Continue if we're taking the new resource.
-
- if (FileReference* f = valueCast<FileReference>(srcConfigValue->value.get())) {
- std::unique_ptr<FileReference> newFileRef;
- if (manglePackage) {
- newFileRef = cloneAndMangleFile(srcPackage->name, *f);
- } else {
- newFileRef = std::unique_ptr<FileReference>(f->clone(
- &mMasterTable->stringPool));
- }
-
- if (callback) {
- if (!callback(resName, srcConfigValue->config, newFileRef.get(), f)) {
- error = true;
- continue;
- }
- }
- dstConfigValue->value = std::move(newFileRef);
-
- } else {
- dstConfigValue->value = std::unique_ptr<Value>(srcConfigValue->value->clone(
- &mMasterTable->stringPool));
- }
- }
+ } else {
+ dstConfigValue->value = std::unique_ptr<Value>(
+ srcConfigValue->value->clone(&mMasterTable->stringPool));
}
+ }
}
- return !error;
+ }
+ return !error;
}
-std::unique_ptr<FileReference> TableMerger::cloneAndMangleFile(const std::string& package,
- const FileReference& fileRef) {
- StringPiece prefix, entry, suffix;
- if (util::extractResFilePathParts(*fileRef.path, &prefix, &entry, &suffix)) {
- std::string mangledEntry = NameMangler::mangleEntry(package, entry.toString());
- std::string newPath = prefix.toString() + mangledEntry + suffix.toString();
- std::unique_ptr<FileReference> newFileRef = util::make_unique<FileReference>(
- mMasterTable->stringPool.makeRef(newPath));
- newFileRef->setComment(fileRef.getComment());
- newFileRef->setSource(fileRef.getSource());
- return newFileRef;
- }
- return std::unique_ptr<FileReference>(fileRef.clone(&mMasterTable->stringPool));
+std::unique_ptr<FileReference> TableMerger::cloneAndMangleFile(
+ const std::string& package, const FileReference& fileRef) {
+ StringPiece prefix, entry, suffix;
+ if (util::extractResFilePathParts(*fileRef.path, &prefix, &entry, &suffix)) {
+ std::string mangledEntry =
+ NameMangler::mangleEntry(package, entry.toString());
+ std::string newPath = prefix.toString() + mangledEntry + suffix.toString();
+ std::unique_ptr<FileReference> newFileRef =
+ util::make_unique<FileReference>(
+ mMasterTable->stringPool.makeRef(newPath));
+ newFileRef->setComment(fileRef.getComment());
+ newFileRef->setSource(fileRef.getSource());
+ return newFileRef;
+ }
+ return std::unique_ptr<FileReference>(
+ fileRef.clone(&mMasterTable->stringPool));
}
-bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay) {
- ResourceTable table;
- std::string path = ResourceUtils::buildResourceFileName(fileDesc, nullptr);
- std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
- table.stringPool.makeRef(path));
- fileRef->setSource(fileDesc.source);
- fileRef->file = file;
+bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file,
+ bool overlay) {
+ ResourceTable table;
+ std::string path = ResourceUtils::buildResourceFileName(fileDesc, nullptr);
+ std::unique_ptr<FileReference> fileRef =
+ util::make_unique<FileReference>(table.stringPool.makeRef(path));
+ fileRef->setSource(fileDesc.source);
+ fileRef->file = file;
- ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0);
- pkg->findOrCreateType(fileDesc.name.type)
- ->findOrCreateEntry(fileDesc.name.entry)
- ->findOrCreateValue(fileDesc.config, {})
- ->value = std::move(fileRef);
+ ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0);
+ pkg->findOrCreateType(fileDesc.name.type)
+ ->findOrCreateEntry(fileDesc.name.entry)
+ ->findOrCreateValue(fileDesc.config, {})
+ ->value = std::move(fileRef);
- return doMerge(file->getSource(), &table, pkg,
- false /* mangle */, overlay /* overlay */, true /* allow new */, {});
+ return doMerge(file->getSource(), &table, pkg, false /* mangle */,
+ overlay /* overlay */, true /* allow new */, {});
}
bool TableMerger::mergeFile(const ResourceFile& fileDesc, io::IFile* file) {
- return mergeFileImpl(fileDesc, file, false /* overlay */);
+ return mergeFileImpl(fileDesc, file, false /* overlay */);
}
-bool TableMerger::mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file) {
- return mergeFileImpl(fileDesc, file, true /* overlay */);
+bool TableMerger::mergeFileOverlay(const ResourceFile& fileDesc,
+ io::IFile* file) {
+ return mergeFileImpl(fileDesc, file, true /* overlay */);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 3473a27..c2e7181 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -31,96 +31,111 @@
namespace aapt {
struct TableMergerOptions {
- /**
- * If true, resources in overlays can be added without previously having existed.
- */
- bool autoAddOverlay = false;
+ /**
+ * If true, resources in overlays can be added without previously having
+ * existed.
+ */
+ bool autoAddOverlay = false;
};
/**
- * TableMerger takes resource tables and merges all packages within the tables that have the same
+ * TableMerger takes resource tables and merges all packages within the tables
+ * that have the same
* package ID.
*
- * If a package has a different name, all the entries in that table have their names mangled
- * to include the package name. This way there are no collisions. In order to do this correctly,
- * the TableMerger needs to also mangle any FileReference paths. Once these are mangled,
- * the original source path of the file, along with the new destination path is recorded in the
+ * If a package has a different name, all the entries in that table have their
+ * names mangled
+ * to include the package name. This way there are no collisions. In order to do
+ * this correctly,
+ * the TableMerger needs to also mangle any FileReference paths. Once these are
+ * mangled,
+ * the original source path of the file, along with the new destination path is
+ * recorded in the
* queue returned from getFileMergeQueue().
*
- * Once the merging is complete, a separate process can go collect the files from the various
- * source APKs and either copy or process their XML and put them in the correct location in
+ * Once the merging is complete, a separate process can go collect the files
+ * from the various
+ * source APKs and either copy or process their XML and put them in the correct
+ * location in
* the final APK.
*/
class TableMerger {
-public:
- /**
- * Note: The outTable ResourceTable must live longer than this TableMerger. References
- * are made to this ResourceTable for efficiency reasons.
- */
- TableMerger(IAaptContext* context, ResourceTable* outTable, const TableMergerOptions& options);
+ public:
+ /**
+ * Note: The outTable ResourceTable must live longer than this TableMerger.
+ * References
+ * are made to this ResourceTable for efficiency reasons.
+ */
+ TableMerger(IAaptContext* context, ResourceTable* outTable,
+ const TableMergerOptions& options);
- const std::set<std::string>& getMergedPackages() const {
- return mMergedPackages;
- }
+ const std::set<std::string>& getMergedPackages() const {
+ return mMergedPackages;
+ }
- /**
- * Merges resources from the same or empty package. This is for local sources.
- * An io::IFileCollection is optional and used to find the referenced Files and process them.
- */
- bool merge(const Source& src, ResourceTable* table,
- io::IFileCollection* collection = nullptr);
+ /**
+ * Merges resources from the same or empty package. This is for local sources.
+ * An io::IFileCollection is optional and used to find the referenced Files
+ * and process them.
+ */
+ bool merge(const Source& src, ResourceTable* table,
+ io::IFileCollection* collection = nullptr);
- /**
- * Merges resources from an overlay ResourceTable.
- * An io::IFileCollection is optional and used to find the referenced Files and process them.
- */
- bool mergeOverlay(const Source& src, ResourceTable* table,
- io::IFileCollection* collection = nullptr);
+ /**
+ * Merges resources from an overlay ResourceTable.
+ * An io::IFileCollection is optional and used to find the referenced Files
+ * and process them.
+ */
+ bool mergeOverlay(const Source& src, ResourceTable* table,
+ io::IFileCollection* collection = nullptr);
- /**
- * Merges resources from the given package, mangling the name. This is for static libraries.
- * An io::IFileCollection is needed in order to find the referenced Files and process them.
- */
- bool mergeAndMangle(const Source& src, const StringPiece& package, ResourceTable* table,
- io::IFileCollection* collection);
+ /**
+ * Merges resources from the given package, mangling the name. This is for
+ * static libraries.
+ * An io::IFileCollection is needed in order to find the referenced Files and
+ * process them.
+ */
+ bool mergeAndMangle(const Source& src, const StringPiece& package,
+ ResourceTable* table, io::IFileCollection* collection);
- /**
- * Merges a compiled file that belongs to this same or empty package. This is for local sources.
- */
- bool mergeFile(const ResourceFile& fileDesc, io::IFile* file);
+ /**
+ * Merges a compiled file that belongs to this same or empty package. This is
+ * for local sources.
+ */
+ bool mergeFile(const ResourceFile& fileDesc, io::IFile* file);
- /**
- * Merges a compiled file from an overlay, overriding an existing definition.
- */
- bool mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file);
+ /**
+ * Merges a compiled file from an overlay, overriding an existing definition.
+ */
+ bool mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file);
-private:
- using FileMergeCallback = std::function<bool(const ResourceNameRef&,
- const ConfigDescription& config,
- FileReference*, FileReference*)>;
+ private:
+ using FileMergeCallback = std::function<bool(const ResourceNameRef&,
+ const ConfigDescription& config,
+ FileReference*, FileReference*)>;
- IAaptContext* mContext;
- ResourceTable* mMasterTable;
- TableMergerOptions mOptions;
- ResourceTablePackage* mMasterPackage;
+ IAaptContext* mContext;
+ ResourceTable* mMasterTable;
+ TableMergerOptions mOptions;
+ ResourceTablePackage* mMasterPackage;
- std::set<std::string> mMergedPackages;
+ std::set<std::string> mMergedPackages;
- bool mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay);
+ bool mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file,
+ bool overlay);
- bool mergeImpl(const Source& src, ResourceTable* srcTable, io::IFileCollection* collection,
- bool overlay, bool allowNew);
+ bool mergeImpl(const Source& src, ResourceTable* srcTable,
+ io::IFileCollection* collection, bool overlay, bool allowNew);
- bool doMerge(const Source& src, ResourceTable* srcTable, ResourceTablePackage* srcPackage,
- const bool manglePackage,
- const bool overlay,
- const bool allowNewResources,
- const FileMergeCallback& callback);
+ bool doMerge(const Source& src, ResourceTable* srcTable,
+ ResourceTablePackage* srcPackage, const bool manglePackage,
+ const bool overlay, const bool allowNewResources,
+ const FileMergeCallback& callback);
- std::unique_ptr<FileReference> cloneAndMangleFile(const std::string& package,
- const FileReference& value);
+ std::unique_ptr<FileReference> cloneAndMangleFile(const std::string& package,
+ const FileReference& value);
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_TABLEMERGER_H */
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index fb1cb21..e0b2b66 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -14,9 +14,9 @@
* limitations under the License.
*/
+#include "link/TableMerger.h"
#include "filter/ConfigFilter.h"
#include "io/FileSystem.h"
-#include "link/TableMerger.h"
#include "test/Builders.h"
#include "test/Context.h"
@@ -25,292 +25,327 @@
namespace aapt {
struct TableMergerTest : public ::testing::Test {
- std::unique_ptr<IAaptContext> mContext;
+ std::unique_ptr<IAaptContext> mContext;
- void SetUp() override {
- mContext = test::ContextBuilder()
- // We are compiling this package.
- .setCompilationPackage("com.app.a")
+ void SetUp() override {
+ mContext =
+ test::ContextBuilder()
+ // We are compiling this package.
+ .setCompilationPackage("com.app.a")
- // Merge all packages that have this package ID.
- .setPackageId(0x7f)
+ // Merge all packages that have this package ID.
+ .setPackageId(0x7f)
- // Mangle all packages that do not have this package name.
- .setNameManglerPolicy(NameManglerPolicy{ "com.app.a", { "com.app.b" } })
+ // Mangle all packages that do not have this package name.
+ .setNameManglerPolicy(NameManglerPolicy{"com.app.a", {"com.app.b"}})
- .build();
- }
+ .build();
+ }
};
TEST_F(TableMergerTest, SimpleMerge) {
- std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
- .setPackageId("com.app.a", 0x7f)
- .addReference("com.app.a:id/foo", "com.app.a:id/bar")
- .addReference("com.app.a:id/bar", "com.app.b:id/foo")
- .addValue("com.app.a:styleable/view", test::StyleableBuilder()
- .addItem("com.app.b:id/foo")
- .build())
- .build();
+ std::unique_ptr<ResourceTable> tableA =
+ test::ResourceTableBuilder()
+ .setPackageId("com.app.a", 0x7f)
+ .addReference("com.app.a:id/foo", "com.app.a:id/bar")
+ .addReference("com.app.a:id/bar", "com.app.b:id/foo")
+ .addValue(
+ "com.app.a:styleable/view",
+ test::StyleableBuilder().addItem("com.app.b:id/foo").build())
+ .build();
- std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
- .setPackageId("com.app.b", 0x7f)
- .addSimple("com.app.b:id/foo")
- .build();
+ std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
+ .setPackageId("com.app.b", 0x7f)
+ .addSimple("com.app.b:id/foo")
+ .build();
- ResourceTable finalTable;
- TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{});
- io::FileCollection collection;
+ ResourceTable finalTable;
+ TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{});
+ io::FileCollection collection;
- ASSERT_TRUE(merger.merge({}, tableA.get()));
- ASSERT_TRUE(merger.mergeAndMangle({}, "com.app.b", tableB.get(), &collection));
+ ASSERT_TRUE(merger.merge({}, tableA.get()));
+ ASSERT_TRUE(
+ merger.mergeAndMangle({}, "com.app.b", tableB.get(), &collection));
- EXPECT_TRUE(merger.getMergedPackages().count("com.app.b") != 0);
+ EXPECT_TRUE(merger.getMergedPackages().count("com.app.b") != 0);
- // Entries from com.app.a should not be mangled.
- AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("com.app.a:id/foo")));
- AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("com.app.a:id/bar")));
- AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("com.app.a:styleable/view")));
+ // Entries from com.app.a should not be mangled.
+ AAPT_EXPECT_TRUE(
+ finalTable.findResource(test::parseNameOrDie("com.app.a:id/foo")));
+ AAPT_EXPECT_TRUE(
+ finalTable.findResource(test::parseNameOrDie("com.app.a:id/bar")));
+ AAPT_EXPECT_TRUE(finalTable.findResource(
+ test::parseNameOrDie("com.app.a:styleable/view")));
- // The unmangled name should not be present.
- AAPT_EXPECT_FALSE(finalTable.findResource(test::parseNameOrDie("com.app.b:id/foo")));
+ // The unmangled name should not be present.
+ AAPT_EXPECT_FALSE(
+ finalTable.findResource(test::parseNameOrDie("com.app.b:id/foo")));
- // Look for the mangled name.
- AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("com.app.a:id/com.app.b$foo")));
+ // Look for the mangled name.
+ AAPT_EXPECT_TRUE(finalTable.findResource(
+ test::parseNameOrDie("com.app.a:id/com.app.b$foo")));
}
TEST_F(TableMergerTest, MergeFile) {
- ResourceTable finalTable;
- TableMergerOptions options;
- options.autoAddOverlay = false;
- TableMerger merger(mContext.get(), &finalTable, options);
+ ResourceTable finalTable;
+ TableMergerOptions options;
+ options.autoAddOverlay = false;
+ TableMerger merger(mContext.get(), &finalTable, options);
- ResourceFile fileDesc;
- fileDesc.config = test::parseConfigOrDie("hdpi-v4");
- fileDesc.name = test::parseNameOrDie("layout/main");
- fileDesc.source = Source("res/layout-hdpi/main.xml");
- test::TestFile testFile("path/to/res/layout-hdpi/main.xml.flat");
+ ResourceFile fileDesc;
+ fileDesc.config = test::parseConfigOrDie("hdpi-v4");
+ fileDesc.name = test::parseNameOrDie("layout/main");
+ fileDesc.source = Source("res/layout-hdpi/main.xml");
+ test::TestFile testFile("path/to/res/layout-hdpi/main.xml.flat");
- ASSERT_TRUE(merger.mergeFile(fileDesc, &testFile));
+ ASSERT_TRUE(merger.mergeFile(fileDesc, &testFile));
- FileReference* file = test::getValueForConfig<FileReference>(&finalTable,
- "com.app.a:layout/main",
- test::parseConfigOrDie("hdpi-v4"));
- ASSERT_NE(nullptr, file);
- EXPECT_EQ(std::string("res/layout-hdpi-v4/main.xml"), *file->path);
+ FileReference* file = test::getValueForConfig<FileReference>(
+ &finalTable, "com.app.a:layout/main", test::parseConfigOrDie("hdpi-v4"));
+ ASSERT_NE(nullptr, file);
+ EXPECT_EQ(std::string("res/layout-hdpi-v4/main.xml"), *file->path);
}
TEST_F(TableMergerTest, MergeFileOverlay) {
- ResourceTable finalTable;
- TableMergerOptions tableMergerOptions;
- tableMergerOptions.autoAddOverlay = false;
- TableMerger merger(mContext.get(), &finalTable, tableMergerOptions);
+ ResourceTable finalTable;
+ TableMergerOptions tableMergerOptions;
+ tableMergerOptions.autoAddOverlay = false;
+ TableMerger merger(mContext.get(), &finalTable, tableMergerOptions);
- ResourceFile fileDesc;
- fileDesc.name = test::parseNameOrDie("xml/foo");
- test::TestFile fileA("path/to/fileA.xml.flat");
- test::TestFile fileB("path/to/fileB.xml.flat");
+ ResourceFile fileDesc;
+ fileDesc.name = test::parseNameOrDie("xml/foo");
+ test::TestFile fileA("path/to/fileA.xml.flat");
+ test::TestFile fileB("path/to/fileB.xml.flat");
- ASSERT_TRUE(merger.mergeFile(fileDesc, &fileA));
- ASSERT_TRUE(merger.mergeFileOverlay(fileDesc, &fileB));
+ ASSERT_TRUE(merger.mergeFile(fileDesc, &fileA));
+ ASSERT_TRUE(merger.mergeFileOverlay(fileDesc, &fileB));
}
TEST_F(TableMergerTest, MergeFileReferences) {
- std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
- .setPackageId("com.app.a", 0x7f)
- .addFileReference("com.app.a:xml/file", "res/xml/file.xml")
- .build();
- std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
- .setPackageId("com.app.b", 0x7f)
- .addFileReference("com.app.b:xml/file", "res/xml/file.xml")
- .build();
+ std::unique_ptr<ResourceTable> tableA =
+ test::ResourceTableBuilder()
+ .setPackageId("com.app.a", 0x7f)
+ .addFileReference("com.app.a:xml/file", "res/xml/file.xml")
+ .build();
+ std::unique_ptr<ResourceTable> tableB =
+ test::ResourceTableBuilder()
+ .setPackageId("com.app.b", 0x7f)
+ .addFileReference("com.app.b:xml/file", "res/xml/file.xml")
+ .build();
- ResourceTable finalTable;
- TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{});
- io::FileCollection collection;
- collection.insertFile("res/xml/file.xml");
+ ResourceTable finalTable;
+ TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{});
+ io::FileCollection collection;
+ collection.insertFile("res/xml/file.xml");
- ASSERT_TRUE(merger.merge({}, tableA.get()));
- ASSERT_TRUE(merger.mergeAndMangle({}, "com.app.b", tableB.get(), &collection));
+ ASSERT_TRUE(merger.merge({}, tableA.get()));
+ ASSERT_TRUE(
+ merger.mergeAndMangle({}, "com.app.b", tableB.get(), &collection));
- FileReference* f = test::getValue<FileReference>(&finalTable, "com.app.a:xml/file");
- ASSERT_NE(f, nullptr);
- EXPECT_EQ(std::string("res/xml/file.xml"), *f->path);
+ FileReference* f =
+ test::getValue<FileReference>(&finalTable, "com.app.a:xml/file");
+ ASSERT_NE(f, nullptr);
+ EXPECT_EQ(std::string("res/xml/file.xml"), *f->path);
- f = test::getValue<FileReference>(&finalTable, "com.app.a:xml/com.app.b$file");
- ASSERT_NE(f, nullptr);
- EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), *f->path);
+ f = test::getValue<FileReference>(&finalTable,
+ "com.app.a:xml/com.app.b$file");
+ ASSERT_NE(f, nullptr);
+ EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), *f->path);
}
TEST_F(TableMergerTest, OverrideResourceWithOverlay) {
- std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder()
- .setPackageId("", 0x00)
- .addValue("bool/foo", ResourceUtils::tryParseBool("true"))
- .build();
- std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder()
- .setPackageId("", 0x00)
- .addValue("bool/foo", ResourceUtils::tryParseBool("false"))
- .build();
+ std::unique_ptr<ResourceTable> base =
+ test::ResourceTableBuilder()
+ .setPackageId("", 0x00)
+ .addValue("bool/foo", ResourceUtils::tryParseBool("true"))
+ .build();
+ std::unique_ptr<ResourceTable> overlay =
+ test::ResourceTableBuilder()
+ .setPackageId("", 0x00)
+ .addValue("bool/foo", ResourceUtils::tryParseBool("false"))
+ .build();
- ResourceTable finalTable;
- TableMergerOptions tableMergerOptions;
- tableMergerOptions.autoAddOverlay = false;
- TableMerger merger(mContext.get(), &finalTable, tableMergerOptions);
+ ResourceTable finalTable;
+ TableMergerOptions tableMergerOptions;
+ tableMergerOptions.autoAddOverlay = false;
+ TableMerger merger(mContext.get(), &finalTable, tableMergerOptions);
- ASSERT_TRUE(merger.merge({}, base.get()));
- ASSERT_TRUE(merger.mergeOverlay({}, overlay.get()));
+ ASSERT_TRUE(merger.merge({}, base.get()));
+ ASSERT_TRUE(merger.mergeOverlay({}, overlay.get()));
- BinaryPrimitive* foo = test::getValue<BinaryPrimitive>(&finalTable, "com.app.a:bool/foo");
- ASSERT_NE(nullptr, foo);
- EXPECT_EQ(0x0u, foo->value.data);
+ BinaryPrimitive* foo =
+ test::getValue<BinaryPrimitive>(&finalTable, "com.app.a:bool/foo");
+ ASSERT_NE(nullptr, foo);
+ EXPECT_EQ(0x0u, foo->value.data);
}
TEST_F(TableMergerTest, OverrideSameResourceIdsWithOverlay) {
- std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder()
- .setPackageId("", 0x7f)
- .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), SymbolState::kPublic)
- .build();
- std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder()
- .setPackageId("", 0x7f)
- .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), SymbolState::kPublic)
- .build();
+ std::unique_ptr<ResourceTable> base =
+ test::ResourceTableBuilder()
+ .setPackageId("", 0x7f)
+ .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
+ SymbolState::kPublic)
+ .build();
+ std::unique_ptr<ResourceTable> overlay =
+ test::ResourceTableBuilder()
+ .setPackageId("", 0x7f)
+ .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
+ SymbolState::kPublic)
+ .build();
- ResourceTable finalTable;
- TableMergerOptions tableMergerOptions;
- tableMergerOptions.autoAddOverlay = false;
- TableMerger merger(mContext.get(), &finalTable, tableMergerOptions);
+ ResourceTable finalTable;
+ TableMergerOptions tableMergerOptions;
+ tableMergerOptions.autoAddOverlay = false;
+ TableMerger merger(mContext.get(), &finalTable, tableMergerOptions);
- ASSERT_TRUE(merger.merge({}, base.get()));
- ASSERT_TRUE(merger.mergeOverlay({}, overlay.get()));
+ ASSERT_TRUE(merger.merge({}, base.get()));
+ ASSERT_TRUE(merger.mergeOverlay({}, overlay.get()));
}
TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) {
- std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder()
- .setPackageId("", 0x7f)
- .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), SymbolState::kPublic)
- .build();
- std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder()
- .setPackageId("", 0x7f)
- .setSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001), SymbolState::kPublic)
- .build();
+ std::unique_ptr<ResourceTable> base =
+ test::ResourceTableBuilder()
+ .setPackageId("", 0x7f)
+ .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
+ SymbolState::kPublic)
+ .build();
+ std::unique_ptr<ResourceTable> overlay =
+ test::ResourceTableBuilder()
+ .setPackageId("", 0x7f)
+ .setSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001),
+ SymbolState::kPublic)
+ .build();
- ResourceTable finalTable;
- TableMergerOptions tableMergerOptions;
- tableMergerOptions.autoAddOverlay = false;
- TableMerger merger(mContext.get(), &finalTable, tableMergerOptions);
+ ResourceTable finalTable;
+ TableMergerOptions tableMergerOptions;
+ tableMergerOptions.autoAddOverlay = false;
+ TableMerger merger(mContext.get(), &finalTable, tableMergerOptions);
- ASSERT_TRUE(merger.merge({}, base.get()));
- ASSERT_FALSE(merger.mergeOverlay({}, overlay.get()));
+ ASSERT_TRUE(merger.merge({}, base.get()));
+ ASSERT_FALSE(merger.mergeOverlay({}, overlay.get()));
}
TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) {
- std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder()
- .setPackageId("", 0x7f)
- .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), SymbolState::kPublic)
- .build();
- std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder()
- .setPackageId("", 0x7f)
- .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002), SymbolState::kPublic)
- .build();
+ std::unique_ptr<ResourceTable> base =
+ test::ResourceTableBuilder()
+ .setPackageId("", 0x7f)
+ .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
+ SymbolState::kPublic)
+ .build();
+ std::unique_ptr<ResourceTable> overlay =
+ test::ResourceTableBuilder()
+ .setPackageId("", 0x7f)
+ .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002),
+ SymbolState::kPublic)
+ .build();
- ResourceTable finalTable;
- TableMergerOptions tableMergerOptions;
- tableMergerOptions.autoAddOverlay = false;
- TableMerger merger(mContext.get(), &finalTable, tableMergerOptions);
+ ResourceTable finalTable;
+ TableMergerOptions tableMergerOptions;
+ tableMergerOptions.autoAddOverlay = false;
+ TableMerger merger(mContext.get(), &finalTable, tableMergerOptions);
- ASSERT_TRUE(merger.merge({}, base.get()));
- ASSERT_FALSE(merger.mergeOverlay({}, overlay.get()));
+ ASSERT_TRUE(merger.merge({}, base.get()));
+ ASSERT_FALSE(merger.mergeOverlay({}, overlay.get()));
}
TEST_F(TableMergerTest, MergeAddResourceFromOverlay) {
- std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
- .setPackageId("", 0x7f)
- .setSymbolState("bool/foo", {}, SymbolState::kUndefined)
- .build();
- std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
- .setPackageId("", 0x7f)
- .addValue("bool/foo", ResourceUtils::tryParseBool("true"))
- .build();
+ std::unique_ptr<ResourceTable> tableA =
+ test::ResourceTableBuilder()
+ .setPackageId("", 0x7f)
+ .setSymbolState("bool/foo", {}, SymbolState::kUndefined)
+ .build();
+ std::unique_ptr<ResourceTable> tableB =
+ test::ResourceTableBuilder()
+ .setPackageId("", 0x7f)
+ .addValue("bool/foo", ResourceUtils::tryParseBool("true"))
+ .build();
- ResourceTable finalTable;
- TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{});
+ ResourceTable finalTable;
+ TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{});
- ASSERT_TRUE(merger.merge({}, tableA.get()));
- ASSERT_TRUE(merger.mergeOverlay({}, tableB.get()));
+ ASSERT_TRUE(merger.merge({}, tableA.get()));
+ ASSERT_TRUE(merger.mergeOverlay({}, tableB.get()));
}
TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) {
- std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
- .setPackageId("", 0x7f)
- .build();
- std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
- .setPackageId("", 0x7f)
- .addValue("bool/foo", ResourceUtils::tryParseBool("true"))
- .build();
+ std::unique_ptr<ResourceTable> tableA =
+ test::ResourceTableBuilder().setPackageId("", 0x7f).build();
+ std::unique_ptr<ResourceTable> tableB =
+ test::ResourceTableBuilder()
+ .setPackageId("", 0x7f)
+ .addValue("bool/foo", ResourceUtils::tryParseBool("true"))
+ .build();
- ResourceTable finalTable;
- TableMergerOptions options;
- options.autoAddOverlay = true;
- TableMerger merger(mContext.get(), &finalTable, options);
+ ResourceTable finalTable;
+ TableMergerOptions options;
+ options.autoAddOverlay = true;
+ TableMerger merger(mContext.get(), &finalTable, options);
- ASSERT_TRUE(merger.merge({}, tableA.get()));
- ASSERT_TRUE(merger.mergeOverlay({}, tableB.get()));
+ ASSERT_TRUE(merger.merge({}, tableA.get()));
+ ASSERT_TRUE(merger.mergeOverlay({}, tableB.get()));
}
TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) {
- std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
- .setPackageId("", 0x7f)
- .build();
- std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
- .setPackageId("", 0x7f)
- .addValue("bool/foo", ResourceUtils::tryParseBool("true"))
- .build();
+ std::unique_ptr<ResourceTable> tableA =
+ test::ResourceTableBuilder().setPackageId("", 0x7f).build();
+ std::unique_ptr<ResourceTable> tableB =
+ test::ResourceTableBuilder()
+ .setPackageId("", 0x7f)
+ .addValue("bool/foo", ResourceUtils::tryParseBool("true"))
+ .build();
- ResourceTable finalTable;
- TableMergerOptions options;
- options.autoAddOverlay = false;
- TableMerger merger(mContext.get(), &finalTable, options);
+ ResourceTable finalTable;
+ TableMergerOptions options;
+ options.autoAddOverlay = false;
+ TableMerger merger(mContext.get(), &finalTable, options);
- ASSERT_TRUE(merger.merge({}, tableA.get()));
- ASSERT_FALSE(merger.mergeOverlay({}, tableB.get()));
+ ASSERT_TRUE(merger.merge({}, tableA.get()));
+ ASSERT_FALSE(merger.mergeOverlay({}, tableB.get()));
}
TEST_F(TableMergerTest, OverlaidStyleablesShouldBeMerged) {
- std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
- .setPackageId("com.app.a", 0x7f)
- .addValue("com.app.a:styleable/Foo", test::StyleableBuilder()
- .addItem("com.app.a:attr/bar")
- .addItem("com.app.a:attr/foo", ResourceId(0x01010000))
- .build())
- .build();
+ std::unique_ptr<ResourceTable> tableA =
+ test::ResourceTableBuilder()
+ .setPackageId("com.app.a", 0x7f)
+ .addValue("com.app.a:styleable/Foo",
+ test::StyleableBuilder()
+ .addItem("com.app.a:attr/bar")
+ .addItem("com.app.a:attr/foo", ResourceId(0x01010000))
+ .build())
+ .build();
- std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
- .setPackageId("com.app.a", 0x7f)
- .addValue("com.app.a:styleable/Foo", test::StyleableBuilder()
- .addItem("com.app.a:attr/bat")
- .addItem("com.app.a:attr/foo")
- .build())
- .build();
+ std::unique_ptr<ResourceTable> tableB =
+ test::ResourceTableBuilder()
+ .setPackageId("com.app.a", 0x7f)
+ .addValue("com.app.a:styleable/Foo",
+ test::StyleableBuilder()
+ .addItem("com.app.a:attr/bat")
+ .addItem("com.app.a:attr/foo")
+ .build())
+ .build();
- ResourceTable finalTable;
- TableMergerOptions options;
- options.autoAddOverlay = true;
- TableMerger merger(mContext.get(), &finalTable, options);
+ ResourceTable finalTable;
+ TableMergerOptions options;
+ options.autoAddOverlay = true;
+ TableMerger merger(mContext.get(), &finalTable, options);
- ASSERT_TRUE(merger.merge({}, tableA.get()));
- ASSERT_TRUE(merger.mergeOverlay({}, tableB.get()));
+ ASSERT_TRUE(merger.merge({}, tableA.get()));
+ ASSERT_TRUE(merger.mergeOverlay({}, tableB.get()));
- Debug::printTable(&finalTable, {});
+ Debug::printTable(&finalTable, {});
- Styleable* styleable = test::getValue<Styleable>(&finalTable, "com.app.a:styleable/Foo");
- ASSERT_NE(nullptr, styleable);
+ Styleable* styleable =
+ test::getValue<Styleable>(&finalTable, "com.app.a:styleable/Foo");
+ ASSERT_NE(nullptr, styleable);
- std::vector<Reference> expectedRefs = {
- Reference(test::parseNameOrDie("com.app.a:attr/bar")),
- Reference(test::parseNameOrDie("com.app.a:attr/bat")),
- Reference(test::parseNameOrDie("com.app.a:attr/foo"), ResourceId(0x01010000)),
- };
+ std::vector<Reference> expectedRefs = {
+ Reference(test::parseNameOrDie("com.app.a:attr/bar")),
+ Reference(test::parseNameOrDie("com.app.a:attr/bat")),
+ Reference(test::parseNameOrDie("com.app.a:attr/foo"),
+ ResourceId(0x01010000)),
+ };
- EXPECT_EQ(expectedRefs, styleable->entries);
+ EXPECT_EQ(expectedRefs, styleable->entries);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/VersionCollapser.cpp b/tools/aapt2/link/VersionCollapser.cpp
index 949d656..61a1f86 100644
--- a/tools/aapt2/link/VersionCollapser.cpp
+++ b/tools/aapt2/link/VersionCollapser.cpp
@@ -24,129 +24,140 @@
template <typename Iterator, typename Pred>
class FilterIterator {
-public:
- FilterIterator(Iterator begin, Iterator end, Pred pred=Pred()) :
- mCurrent(begin), mEnd(end), mPred(pred) {
- advance();
- }
+ public:
+ FilterIterator(Iterator begin, Iterator end, Pred pred = Pred())
+ : mCurrent(begin), mEnd(end), mPred(pred) {
+ advance();
+ }
- bool hasNext() {
- return mCurrent != mEnd;
- }
+ bool hasNext() { return mCurrent != mEnd; }
- Iterator nextIter() {
- Iterator iter = mCurrent;
- ++mCurrent;
- advance();
- return iter;
- }
+ Iterator nextIter() {
+ Iterator iter = mCurrent;
+ ++mCurrent;
+ advance();
+ return iter;
+ }
- typename Iterator::reference next() {
- return *nextIter();
- }
+ typename Iterator::reference next() { return *nextIter(); }
-private:
- void advance() {
- for (; mCurrent != mEnd; ++mCurrent) {
- if (mPred(*mCurrent)) {
- return;
- }
- }
+ private:
+ void advance() {
+ for (; mCurrent != mEnd; ++mCurrent) {
+ if (mPred(*mCurrent)) {
+ return;
+ }
}
+ }
- Iterator mCurrent, mEnd;
- Pred mPred;
+ Iterator mCurrent, mEnd;
+ Pred mPred;
};
template <typename Iterator, typename Pred>
-FilterIterator<Iterator, Pred> makeFilterIterator(Iterator begin, Iterator end=Iterator(),
- Pred pred=Pred()) {
- return FilterIterator<Iterator, Pred>(begin, end, pred);
+FilterIterator<Iterator, Pred> makeFilterIterator(Iterator begin,
+ Iterator end = Iterator(),
+ Pred pred = Pred()) {
+ return FilterIterator<Iterator, Pred>(begin, end, pred);
}
/**
- * Every Configuration with an SDK version specified that is less than minSdk will be removed.
- * The exception is when there is no exact matching resource for the minSdk. The next smallest
+ * Every Configuration with an SDK version specified that is less than minSdk
+ * will be removed.
+ * The exception is when there is no exact matching resource for the minSdk. The
+ * next smallest
* one will be kept.
*/
static void collapseVersions(int minSdk, ResourceEntry* entry) {
- // First look for all sdks less than minSdk.
- for (auto iter = entry->values.rbegin(); iter != entry->values.rend(); ++iter) {
- // Check if the item was already marked for removal.
- if (!(*iter)) {
- continue;
- }
-
- const ConfigDescription& config = (*iter)->config;
- if (config.sdkVersion <= minSdk) {
- // This is the first configuration we've found with a smaller or equal SDK level
- // to the minimum. We MUST keep this one, but remove all others we find, which get
- // overridden by this one.
-
- ConfigDescription configWithoutSdk = config;
- configWithoutSdk.sdkVersion = 0;
- auto pred = [&](const std::unique_ptr<ResourceConfigValue>& val) -> bool {
- // Check that the value hasn't already been marked for removal.
- if (!val) {
- return false;
- }
-
- // Only return Configs that differ in SDK version.
- configWithoutSdk.sdkVersion = val->config.sdkVersion;
- return configWithoutSdk == val->config && val->config.sdkVersion <= minSdk;
- };
-
- // Remove the rest that match.
- auto filterIter = makeFilterIterator(iter + 1, entry->values.rend(), pred);
- while (filterIter.hasNext()) {
- filterIter.next() = {};
- }
- }
+ // First look for all sdks less than minSdk.
+ for (auto iter = entry->values.rbegin(); iter != entry->values.rend();
+ ++iter) {
+ // Check if the item was already marked for removal.
+ if (!(*iter)) {
+ continue;
}
- // Now erase the nullptr values.
- entry->values.erase(std::remove_if(entry->values.begin(), entry->values.end(),
- [](const std::unique_ptr<ResourceConfigValue>& val) -> bool {
- return val == nullptr;
- }), entry->values.end());
+ const ConfigDescription& config = (*iter)->config;
+ if (config.sdkVersion <= minSdk) {
+ // This is the first configuration we've found with a smaller or equal SDK
+ // level
+ // to the minimum. We MUST keep this one, but remove all others we find,
+ // which get
+ // overridden by this one.
- // Strip the version qualifiers for every resource with version <= minSdk. This will ensure
- // that the resource entries are all packed together in the same ResTable_type struct
- // and take up less space in the resources.arsc table.
- bool modified = false;
- for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
- if (configValue->config.sdkVersion != 0 && configValue->config.sdkVersion <= minSdk) {
- // Override the resource with a Configuration without an SDK.
- std::unique_ptr<ResourceConfigValue> newValue = util::make_unique<ResourceConfigValue>(
- configValue->config.copyWithoutSdkVersion(), configValue->product);
- newValue->value = std::move(configValue->value);
- configValue = std::move(newValue);
-
- modified = true;
+ ConfigDescription configWithoutSdk = config;
+ configWithoutSdk.sdkVersion = 0;
+ auto pred = [&](const std::unique_ptr<ResourceConfigValue>& val) -> bool {
+ // Check that the value hasn't already been marked for removal.
+ if (!val) {
+ return false;
}
- }
- if (modified) {
- // We've modified the keys (ConfigDescription) by changing the sdkVersion to 0.
- // We MUST re-sort to ensure ordering guarantees hold.
- std::sort(entry->values.begin(), entry->values.end(),
- [](const std::unique_ptr<ResourceConfigValue>& a,
- const std::unique_ptr<ResourceConfigValue>& b) -> bool {
- return a->config.compare(b->config) < 0;
- });
+ // Only return Configs that differ in SDK version.
+ configWithoutSdk.sdkVersion = val->config.sdkVersion;
+ return configWithoutSdk == val->config &&
+ val->config.sdkVersion <= minSdk;
+ };
+
+ // Remove the rest that match.
+ auto filterIter =
+ makeFilterIterator(iter + 1, entry->values.rend(), pred);
+ while (filterIter.hasNext()) {
+ filterIter.next() = {};
+ }
}
+ }
+
+ // Now erase the nullptr values.
+ entry->values.erase(
+ std::remove_if(entry->values.begin(), entry->values.end(),
+ [](const std::unique_ptr<ResourceConfigValue>& val)
+ -> bool { return val == nullptr; }),
+ entry->values.end());
+
+ // Strip the version qualifiers for every resource with version <= minSdk.
+ // This will ensure
+ // that the resource entries are all packed together in the same ResTable_type
+ // struct
+ // and take up less space in the resources.arsc table.
+ bool modified = false;
+ for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
+ if (configValue->config.sdkVersion != 0 &&
+ configValue->config.sdkVersion <= minSdk) {
+ // Override the resource with a Configuration without an SDK.
+ std::unique_ptr<ResourceConfigValue> newValue =
+ util::make_unique<ResourceConfigValue>(
+ configValue->config.copyWithoutSdkVersion(),
+ configValue->product);
+ newValue->value = std::move(configValue->value);
+ configValue = std::move(newValue);
+
+ modified = true;
+ }
+ }
+
+ if (modified) {
+ // We've modified the keys (ConfigDescription) by changing the sdkVersion to
+ // 0.
+ // We MUST re-sort to ensure ordering guarantees hold.
+ std::sort(entry->values.begin(), entry->values.end(),
+ [](const std::unique_ptr<ResourceConfigValue>& a,
+ const std::unique_ptr<ResourceConfigValue>& b) -> bool {
+ return a->config.compare(b->config) < 0;
+ });
+ }
}
bool VersionCollapser::consume(IAaptContext* context, ResourceTable* table) {
- const int minSdk = context->getMinSdkVersion();
- for (auto& package : table->packages) {
- for (auto& type : package->types) {
- for (auto& entry : type->entries) {
- collapseVersions(minSdk, entry.get());
- }
- }
+ const int minSdk = context->getMinSdkVersion();
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ collapseVersions(minSdk, entry.get());
+ }
}
- return true;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/VersionCollapser_test.cpp b/tools/aapt2/link/VersionCollapser_test.cpp
index dd5f1d1..c0e0ddb 100644
--- a/tools/aapt2/link/VersionCollapser_test.cpp
+++ b/tools/aapt2/link/VersionCollapser_test.cpp
@@ -22,82 +22,96 @@
template <typename T>
using uptr = std::unique_ptr<T>;
-static uptr<ResourceTable> buildTableWithConfigs(const StringPiece& name,
- std::initializer_list<std::string> list) {
- test::ResourceTableBuilder builder;
- for (const std::string& item : list) {
- builder.addSimple(name, test::parseConfigOrDie(item));
- }
- return builder.build();
+static uptr<ResourceTable> buildTableWithConfigs(
+ const StringPiece& name, std::initializer_list<std::string> list) {
+ test::ResourceTableBuilder builder;
+ for (const std::string& item : list) {
+ builder.addSimple(name, test::parseConfigOrDie(item));
+ }
+ return builder.build();
}
TEST(VersionCollapserTest, CollapseVersions) {
- uptr<IAaptContext> context = test::ContextBuilder().setMinSdkVersion(7).build();
+ uptr<IAaptContext> context =
+ test::ContextBuilder().setMinSdkVersion(7).build();
- const StringPiece resName = "@android:string/foo";
+ const StringPiece resName = "@android:string/foo";
- uptr<ResourceTable> table =
- buildTableWithConfigs(resName,
- { "land-v4", "land-v5", "sw600dp", "land-v6",
- "land-v14", "land-v21" });
+ uptr<ResourceTable> table = buildTableWithConfigs(
+ resName,
+ {"land-v4", "land-v5", "sw600dp", "land-v6", "land-v14", "land-v21"});
- VersionCollapser collapser;
- ASSERT_TRUE(collapser.consume(context.get(), table.get()));
+ VersionCollapser collapser;
+ ASSERT_TRUE(collapser.consume(context.get(), table.get()));
- // These should be removed.
- EXPECT_EQ(nullptr,
- test::getValueForConfig<Id>(table.get(), resName, test::parseConfigOrDie("land-v4")));
- EXPECT_EQ(nullptr,
- test::getValueForConfig<Id>(table.get(), resName, test::parseConfigOrDie("land-v5")));
- // This one should be removed because it was renamed to 'land', with the version dropped.
- EXPECT_EQ(nullptr,
- test::getValueForConfig<Id>(table.get(), resName, test::parseConfigOrDie("land-v6")));
+ // These should be removed.
+ EXPECT_EQ(nullptr,
+ test::getValueForConfig<Id>(table.get(), resName,
+ test::parseConfigOrDie("land-v4")));
+ EXPECT_EQ(nullptr,
+ test::getValueForConfig<Id>(table.get(), resName,
+ test::parseConfigOrDie("land-v5")));
+ // This one should be removed because it was renamed to 'land', with the
+ // version dropped.
+ EXPECT_EQ(nullptr,
+ test::getValueForConfig<Id>(table.get(), resName,
+ test::parseConfigOrDie("land-v6")));
- // These should remain.
- EXPECT_NE(nullptr,
- test::getValueForConfig<Id>(table.get(), resName, test::parseConfigOrDie("sw600dp")));
+ // These should remain.
+ EXPECT_NE(nullptr,
+ test::getValueForConfig<Id>(table.get(), resName,
+ test::parseConfigOrDie("sw600dp")));
- // 'land' should be present because it was renamed from 'land-v6'.
- EXPECT_NE(nullptr,
- test::getValueForConfig<Id>(table.get(), resName, test::parseConfigOrDie("land")));
- EXPECT_NE(nullptr,
- test::getValueForConfig<Id>(table.get(), resName, test::parseConfigOrDie("land-v14")));
- EXPECT_NE(nullptr,
- test::getValueForConfig<Id>(table.get(), resName, test::parseConfigOrDie("land-v21")));
+ // 'land' should be present because it was renamed from 'land-v6'.
+ EXPECT_NE(nullptr, test::getValueForConfig<Id>(
+ table.get(), resName, test::parseConfigOrDie("land")));
+ EXPECT_NE(nullptr,
+ test::getValueForConfig<Id>(table.get(), resName,
+ test::parseConfigOrDie("land-v14")));
+ EXPECT_NE(nullptr,
+ test::getValueForConfig<Id>(table.get(), resName,
+ test::parseConfigOrDie("land-v21")));
}
TEST(VersionCollapserTest, CollapseVersionsWhenMinSdkIsHighest) {
- uptr<IAaptContext> context = test::ContextBuilder().setMinSdkVersion(21).build();
+ uptr<IAaptContext> context =
+ test::ContextBuilder().setMinSdkVersion(21).build();
- const StringPiece resName = "@android:string/foo";
+ const StringPiece resName = "@android:string/foo";
- uptr<ResourceTable> table =
- buildTableWithConfigs(resName,
- { "land-v4", "land-v5", "sw600dp", "land-v6",
- "land-v14", "land-v21", "land-v22" });
- VersionCollapser collapser;
- ASSERT_TRUE(collapser.consume(context.get(), table.get()));
+ uptr<ResourceTable> table = buildTableWithConfigs(
+ resName, {"land-v4", "land-v5", "sw600dp", "land-v6", "land-v14",
+ "land-v21", "land-v22"});
+ VersionCollapser collapser;
+ ASSERT_TRUE(collapser.consume(context.get(), table.get()));
- // These should all be removed.
- EXPECT_EQ(nullptr, test::getValueForConfig<Id>(table.get(), resName,
- test::parseConfigOrDie("land-v4")));
- EXPECT_EQ(nullptr, test::getValueForConfig<Id>(table.get(), resName,
- test::parseConfigOrDie("land-v5")));
- EXPECT_EQ(nullptr, test::getValueForConfig<Id>(table.get(), resName,
- test::parseConfigOrDie("land-v6")));
- EXPECT_EQ(nullptr, test::getValueForConfig<Id>(table.get(), resName,
- test::parseConfigOrDie("land-v14")));
+ // These should all be removed.
+ EXPECT_EQ(nullptr,
+ test::getValueForConfig<Id>(table.get(), resName,
+ test::parseConfigOrDie("land-v4")));
+ EXPECT_EQ(nullptr,
+ test::getValueForConfig<Id>(table.get(), resName,
+ test::parseConfigOrDie("land-v5")));
+ EXPECT_EQ(nullptr,
+ test::getValueForConfig<Id>(table.get(), resName,
+ test::parseConfigOrDie("land-v6")));
+ EXPECT_EQ(nullptr,
+ test::getValueForConfig<Id>(table.get(), resName,
+ test::parseConfigOrDie("land-v14")));
- // These should remain.
- EXPECT_NE(nullptr, test::getValueForConfig<Id>(
- table.get(), resName, test::parseConfigOrDie("sw600dp").copyWithoutSdkVersion()));
+ // These should remain.
+ EXPECT_NE(nullptr,
+ test::getValueForConfig<Id>(
+ table.get(), resName,
+ test::parseConfigOrDie("sw600dp").copyWithoutSdkVersion()));
- // land-v21 should have been converted to land.
- EXPECT_NE(nullptr, test::getValueForConfig<Id>(table.get(), resName,
- test::parseConfigOrDie("land")));
- // land-v22 should remain as-is.
- EXPECT_NE(nullptr, test::getValueForConfig<Id>(table.get(), resName,
- test::parseConfigOrDie("land-v22")));
+ // land-v21 should have been converted to land.
+ EXPECT_NE(nullptr, test::getValueForConfig<Id>(
+ table.get(), resName, test::parseConfigOrDie("land")));
+ // land-v22 should remain as-is.
+ EXPECT_NE(nullptr,
+ test::getValueForConfig<Id>(table.get(), resName,
+ test::parseConfigOrDie("land-v22")));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlNamespaceRemover.cpp b/tools/aapt2/link/XmlNamespaceRemover.cpp
index 9f95177..6e8d80d 100644
--- a/tools/aapt2/link/XmlNamespaceRemover.cpp
+++ b/tools/aapt2/link/XmlNamespaceRemover.cpp
@@ -27,57 +27,59 @@
* Visits each xml Node, removing URI references and nested namespaces.
*/
class XmlVisitor : public xml::Visitor {
-public:
- XmlVisitor(bool keepUris) : mKeepUris(keepUris) {
- }
+ public:
+ XmlVisitor(bool keepUris) : mKeepUris(keepUris) {}
- void visit(xml::Element* el) override {
- // Strip namespaces
- for (auto& child : el->children) {
- while (child && xml::nodeCast<xml::Namespace>(child.get())) {
- if (child->children.empty()) {
- child = {};
- } else {
- child = std::move(child->children.front());
- child->parent = el;
- }
- }
+ void visit(xml::Element* el) override {
+ // Strip namespaces
+ for (auto& child : el->children) {
+ while (child && xml::nodeCast<xml::Namespace>(child.get())) {
+ if (child->children.empty()) {
+ child = {};
+ } else {
+ child = std::move(child->children.front());
+ child->parent = el;
}
- el->children.erase(std::remove_if(el->children.begin(), el->children.end(),
- [](const std::unique_ptr<xml::Node>& child) -> bool {
- return child == nullptr;
- }), el->children.end());
-
- if (!mKeepUris) {
- for (xml::Attribute& attr : el->attributes) {
- attr.namespaceUri = std::string();
- }
- el->namespaceUri = std::string();
- }
- xml::Visitor::visit(el);
+ }
}
+ el->children.erase(
+ std::remove_if(el->children.begin(), el->children.end(),
+ [](const std::unique_ptr<xml::Node>& child) -> bool {
+ return child == nullptr;
+ }),
+ el->children.end());
-private:
- bool mKeepUris;
+ if (!mKeepUris) {
+ for (xml::Attribute& attr : el->attributes) {
+ attr.namespaceUri = std::string();
+ }
+ el->namespaceUri = std::string();
+ }
+ xml::Visitor::visit(el);
+ }
+
+ private:
+ bool mKeepUris;
};
-} // namespace
+} // namespace
-bool XmlNamespaceRemover::consume(IAaptContext* context, xml::XmlResource* resource) {
- if (!resource->root) {
- return false;
+bool XmlNamespaceRemover::consume(IAaptContext* context,
+ xml::XmlResource* resource) {
+ if (!resource->root) {
+ return false;
+ }
+ // Replace any root namespaces until the root is a non-namespace node
+ while (xml::nodeCast<xml::Namespace>(resource->root.get())) {
+ if (resource->root->children.empty()) {
+ break;
}
- // Replace any root namespaces until the root is a non-namespace node
- while (xml::nodeCast<xml::Namespace>(resource->root.get())) {
- if (resource->root->children.empty()) {
- break;
- }
- resource->root = std::move(resource->root->children.front());
- resource->root->parent = nullptr;
- }
- XmlVisitor visitor(mKeepUris);
- resource->root->accept(&visitor);
- return true;
+ resource->root = std::move(resource->root->children.front());
+ resource->root->parent = nullptr;
+ }
+ XmlVisitor visitor(mKeepUris);
+ resource->root->accept(&visitor);
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlNamespaceRemover_test.cpp b/tools/aapt2/link/XmlNamespaceRemover_test.cpp
index e72ea439..d2daaee 100644
--- a/tools/aapt2/link/XmlNamespaceRemover_test.cpp
+++ b/tools/aapt2/link/XmlNamespaceRemover_test.cpp
@@ -20,90 +20,92 @@
namespace aapt {
class XmlUriTestVisitor : public xml::Visitor {
-public:
- void visit(xml::Element* el) override {
- for (const auto& attr : el->attributes) {
- EXPECT_EQ(std::string(), attr.namespaceUri);
- }
- EXPECT_EQ(std::string(), el->namespaceUri);
- xml::Visitor::visit(el);
+ public:
+ void visit(xml::Element* el) override {
+ for (const auto& attr : el->attributes) {
+ EXPECT_EQ(std::string(), attr.namespaceUri);
}
+ EXPECT_EQ(std::string(), el->namespaceUri);
+ xml::Visitor::visit(el);
+ }
- void visit(xml::Namespace* ns) override {
- EXPECT_EQ(std::string(), ns->namespaceUri);
- xml::Visitor::visit(ns);
- }
+ void visit(xml::Namespace* ns) override {
+ EXPECT_EQ(std::string(), ns->namespaceUri);
+ xml::Visitor::visit(ns);
+ }
};
class XmlNamespaceTestVisitor : public xml::Visitor {
-public:
- void visit(xml::Namespace* ns) override {
- ADD_FAILURE() << "Detected namespace: "
- << ns->namespacePrefix << "=\"" << ns->namespaceUri << "\"";
- xml::Visitor::visit(ns);
- }
+ public:
+ void visit(xml::Namespace* ns) override {
+ ADD_FAILURE() << "Detected namespace: " << ns->namespacePrefix << "=\""
+ << ns->namespaceUri << "\"";
+ xml::Visitor::visit(ns);
+ }
};
class XmlNamespaceRemoverTest : public ::testing::Test {
-public:
- void SetUp() override {
- mContext = test::ContextBuilder()
- .setCompilationPackage("com.app.test")
- .build();
- }
+ public:
+ void SetUp() override {
+ mContext =
+ test::ContextBuilder().setCompilationPackage("com.app.test").build();
+ }
-protected:
- std::unique_ptr<IAaptContext> mContext;
+ protected:
+ std::unique_ptr<IAaptContext> mContext;
};
TEST_F(XmlNamespaceRemoverTest, RemoveUris) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:text="hello" />)EOF");
- XmlNamespaceRemover remover;
- ASSERT_TRUE(remover.consume(mContext.get(), doc.get()));
+ XmlNamespaceRemover remover;
+ ASSERT_TRUE(remover.consume(mContext.get(), doc.get()));
- xml::Node* root = doc.get()->root.get();
- ASSERT_NE(root, nullptr);
+ xml::Node* root = doc.get()->root.get();
+ ASSERT_NE(root, nullptr);
- XmlUriTestVisitor visitor;
- root->accept(&visitor);
+ XmlUriTestVisitor visitor;
+ root->accept(&visitor);
}
TEST_F(XmlNamespaceRemoverTest, RemoveNamespaces) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:foo="http://schemas.android.com/apk/res/foo"
foo:bar="foobar"
android:text="hello" />)EOF");
- XmlNamespaceRemover remover;
- ASSERT_TRUE(remover.consume(mContext.get(), doc.get()));
+ XmlNamespaceRemover remover;
+ ASSERT_TRUE(remover.consume(mContext.get(), doc.get()));
- xml::Node* root = doc.get()->root.get();
- ASSERT_NE(root, nullptr);
+ xml::Node* root = doc.get()->root.get();
+ ASSERT_NE(root, nullptr);
- XmlNamespaceTestVisitor visitor;
- root->accept(&visitor);
+ XmlNamespaceTestVisitor visitor;
+ root->accept(&visitor);
}
TEST_F(XmlNamespaceRemoverTest, RemoveNestedNamespaces) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:text="hello">
<View xmlns:foo="http://schemas.example.com/foo"
android:text="foo"/>
</View>)EOF");
- XmlNamespaceRemover remover;
- ASSERT_TRUE(remover.consume(mContext.get(), doc.get()));
+ XmlNamespaceRemover remover;
+ ASSERT_TRUE(remover.consume(mContext.get(), doc.get()));
- xml::Node* root = doc.get()->root.get();
- ASSERT_NE(root, nullptr);
+ xml::Node* root = doc.get()->root.get();
+ ASSERT_NE(root, nullptr);
- XmlNamespaceTestVisitor visitor;
- root->accept(&visitor);
+ XmlNamespaceTestVisitor visitor;
+ root->accept(&visitor);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index 59ffe15..945f98a 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -29,146 +29,156 @@
namespace {
/**
- * Visits all references (including parents of styles, references in styles, arrays, etc) and
- * links their symbolic name to their Resource ID, performing mangling and package aliasing
+ * Visits all references (including parents of styles, references in styles,
+ * arrays, etc) and
+ * links their symbolic name to their Resource ID, performing mangling and
+ * package aliasing
* as needed.
*/
class ReferenceVisitor : public ValueVisitor {
-public:
- using ValueVisitor::visit;
+ public:
+ using ValueVisitor::visit;
- ReferenceVisitor(IAaptContext* context, SymbolTable* symbols, xml::IPackageDeclStack* decls,
- CallSite* callSite) :
- mContext(context), mSymbols(symbols), mDecls(decls), mCallSite(callSite),
- mError(false) {
+ ReferenceVisitor(IAaptContext* context, SymbolTable* symbols,
+ xml::IPackageDeclStack* decls, CallSite* callSite)
+ : mContext(context),
+ mSymbols(symbols),
+ mDecls(decls),
+ mCallSite(callSite),
+ mError(false) {}
+
+ void visit(Reference* ref) override {
+ if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mDecls,
+ mCallSite)) {
+ mError = true;
}
+ }
- void visit(Reference* ref) override {
- if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mDecls, mCallSite)) {
- mError = true;
- }
- }
+ bool hasError() const { return mError; }
- bool hasError() const {
- return mError;
- }
-
-private:
- IAaptContext* mContext;
- SymbolTable* mSymbols;
- xml::IPackageDeclStack* mDecls;
- CallSite* mCallSite;
- bool mError;
+ private:
+ IAaptContext* mContext;
+ SymbolTable* mSymbols;
+ xml::IPackageDeclStack* mDecls;
+ CallSite* mCallSite;
+ bool mError;
};
/**
* Visits each xml Element and compiles the attributes within.
*/
class XmlVisitor : public xml::PackageAwareVisitor {
-public:
- using xml::PackageAwareVisitor::visit;
+ public:
+ using xml::PackageAwareVisitor::visit;
- XmlVisitor(IAaptContext* context, SymbolTable* symbols, const Source& source,
- std::set<int>* sdkLevelsFound, CallSite* callSite) :
- mContext(context), mSymbols(symbols), mSource(source), mSdkLevelsFound(sdkLevelsFound),
- mCallSite(callSite), mReferenceVisitor(context, symbols, this, callSite) {
- }
+ XmlVisitor(IAaptContext* context, SymbolTable* symbols, const Source& source,
+ std::set<int>* sdkLevelsFound, CallSite* callSite)
+ : mContext(context),
+ mSymbols(symbols),
+ mSource(source),
+ mSdkLevelsFound(sdkLevelsFound),
+ mCallSite(callSite),
+ mReferenceVisitor(context, symbols, this, callSite) {}
- void visit(xml::Element* el) override {
- const Source source = mSource.withLine(el->lineNumber);
- for (xml::Attribute& attr : el->attributes) {
- Maybe<xml::ExtractedPackage> maybePackage =
- xml::extractPackageFromNamespace(attr.namespaceUri);
- if (maybePackage) {
- // There is a valid package name for this attribute. We will look this up.
- StringPiece package = maybePackage.value().package;
- if (package.empty()) {
- // Empty package means the 'current' or 'local' package.
- package = mContext->getCompilationPackage();
- }
-
- Reference attrRef(ResourceNameRef(package, ResourceType::kAttr, attr.name));
- attrRef.privateReference = maybePackage.value().privateNamespace;
-
- std::string errStr;
- attr.compiledAttribute = ReferenceLinker::compileXmlAttribute(
- attrRef, mContext->getNameMangler(), mSymbols, mCallSite, &errStr);
-
- // Convert the string value into a compiled Value if this is a valid attribute.
- if (attr.compiledAttribute) {
- if (attr.compiledAttribute.value().id) {
- // Record all SDK levels from which the attributes were defined.
- const size_t sdkLevel = findAttributeSdkLevel(
- attr.compiledAttribute.value().id.value());
- if (sdkLevel > 1) {
- mSdkLevelsFound->insert(sdkLevel);
- }
- }
-
- const Attribute* attribute = &attr.compiledAttribute.value().attribute;
- attr.compiledValue = ResourceUtils::tryParseItemForAttribute(attr.value,
- attribute);
- if (!attr.compiledValue &&
- !(attribute->typeMask & android::ResTable_map::TYPE_STRING)) {
- // We won't be able to encode this as a string.
- mContext->getDiagnostics()->error(
- DiagMessage(source) << "'" << attr.value << "' "
- << "is incompatible with attribute "
- << package << ":" << attr.name << " "
- << *attribute);
- mError = true;
- }
-
- } else {
- mContext->getDiagnostics()->error(DiagMessage(source)
- << "attribute '" << package << ":"
- << attr.name << "' " << errStr);
- mError = true;
-
- }
- } else if (!attr.compiledValue) {
- // We still encode references, but only if we haven't manually set this to
- // another compiled value.
- attr.compiledValue = ResourceUtils::tryParseReference(attr.value);
- }
-
- if (attr.compiledValue) {
- // With a compiledValue, we must resolve the reference and assign it an ID.
- attr.compiledValue->setSource(source);
- attr.compiledValue->accept(&mReferenceVisitor);
- }
+ void visit(xml::Element* el) override {
+ const Source source = mSource.withLine(el->lineNumber);
+ for (xml::Attribute& attr : el->attributes) {
+ Maybe<xml::ExtractedPackage> maybePackage =
+ xml::extractPackageFromNamespace(attr.namespaceUri);
+ if (maybePackage) {
+ // There is a valid package name for this attribute. We will look this
+ // up.
+ StringPiece package = maybePackage.value().package;
+ if (package.empty()) {
+ // Empty package means the 'current' or 'local' package.
+ package = mContext->getCompilationPackage();
}
- // Call the super implementation.
- xml::PackageAwareVisitor::visit(el);
+ Reference attrRef(
+ ResourceNameRef(package, ResourceType::kAttr, attr.name));
+ attrRef.privateReference = maybePackage.value().privateNamespace;
+
+ std::string errStr;
+ attr.compiledAttribute = ReferenceLinker::compileXmlAttribute(
+ attrRef, mContext->getNameMangler(), mSymbols, mCallSite, &errStr);
+
+ // Convert the string value into a compiled Value if this is a valid
+ // attribute.
+ if (attr.compiledAttribute) {
+ if (attr.compiledAttribute.value().id) {
+ // Record all SDK levels from which the attributes were defined.
+ const size_t sdkLevel = findAttributeSdkLevel(
+ attr.compiledAttribute.value().id.value());
+ if (sdkLevel > 1) {
+ mSdkLevelsFound->insert(sdkLevel);
+ }
+ }
+
+ const Attribute* attribute =
+ &attr.compiledAttribute.value().attribute;
+ attr.compiledValue =
+ ResourceUtils::tryParseItemForAttribute(attr.value, attribute);
+ if (!attr.compiledValue &&
+ !(attribute->typeMask & android::ResTable_map::TYPE_STRING)) {
+ // We won't be able to encode this as a string.
+ mContext->getDiagnostics()->error(
+ DiagMessage(source) << "'" << attr.value << "' "
+ << "is incompatible with attribute "
+ << package << ":" << attr.name << " "
+ << *attribute);
+ mError = true;
+ }
+
+ } else {
+ mContext->getDiagnostics()->error(DiagMessage(source)
+ << "attribute '" << package << ":"
+ << attr.name << "' " << errStr);
+ mError = true;
+ }
+ } else if (!attr.compiledValue) {
+ // We still encode references, but only if we haven't manually set this
+ // to
+ // another compiled value.
+ attr.compiledValue = ResourceUtils::tryParseReference(attr.value);
+ }
+
+ if (attr.compiledValue) {
+ // With a compiledValue, we must resolve the reference and assign it an
+ // ID.
+ attr.compiledValue->setSource(source);
+ attr.compiledValue->accept(&mReferenceVisitor);
+ }
}
- bool hasError() {
- return mError || mReferenceVisitor.hasError();
- }
+ // Call the super implementation.
+ xml::PackageAwareVisitor::visit(el);
+ }
-private:
- IAaptContext* mContext;
- SymbolTable* mSymbols;
- Source mSource;
- std::set<int>* mSdkLevelsFound;
- CallSite* mCallSite;
- ReferenceVisitor mReferenceVisitor;
- bool mError = false;
+ bool hasError() { return mError || mReferenceVisitor.hasError(); }
+
+ private:
+ IAaptContext* mContext;
+ SymbolTable* mSymbols;
+ Source mSource;
+ std::set<int>* mSdkLevelsFound;
+ CallSite* mCallSite;
+ ReferenceVisitor mReferenceVisitor;
+ bool mError = false;
};
-} // namespace
+} // namespace
-bool XmlReferenceLinker::consume(IAaptContext* context, xml::XmlResource* resource) {
- mSdkLevelsFound.clear();
- CallSite callSite = { resource->file.name };
- XmlVisitor visitor(context, context->getExternalSymbols(), resource->file.source,
- &mSdkLevelsFound, &callSite);
- if (resource->root) {
- resource->root->accept(&visitor);
- return !visitor.hasError();
- }
- return false;
+bool XmlReferenceLinker::consume(IAaptContext* context,
+ xml::XmlResource* resource) {
+ mSdkLevelsFound.clear();
+ CallSite callSite = {resource->file.name};
+ XmlVisitor visitor(context, context->getExternalSymbols(),
+ resource->file.source, &mSdkLevelsFound, &callSite);
+ if (resource->root) {
+ resource->root->accept(&visitor);
+ return !visitor.hasError();
+ }
+ return false;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
index 51eb62c..35d479f 100644
--- a/tools/aapt2/link/XmlReferenceLinker_test.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -20,237 +20,276 @@
namespace aapt {
class XmlReferenceLinkerTest : public ::testing::Test {
-public:
- void SetUp() override {
- mContext = test::ContextBuilder()
- .setCompilationPackage("com.app.test")
- .setNameManglerPolicy(
- NameManglerPolicy{ "com.app.test", { "com.android.support" } })
- .addSymbolSource(test::StaticSymbolSourceBuilder()
- .addPublicSymbol("android:attr/layout_width", ResourceId(0x01010000),
- test::AttributeBuilder()
- .setTypeMask(android::ResTable_map::TYPE_ENUM |
- android::ResTable_map::TYPE_DIMENSION)
- .addItem("match_parent", 0xffffffff)
- .build())
- .addPublicSymbol("android:attr/background", ResourceId(0x01010001),
- test::AttributeBuilder()
- .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
- .addPublicSymbol("android:attr/attr", ResourceId(0x01010002),
- test::AttributeBuilder().build())
- .addPublicSymbol("android:attr/text", ResourceId(0x01010003),
- test::AttributeBuilder()
- .setTypeMask(android::ResTable_map::TYPE_STRING)
- .build())
+ public:
+ void SetUp() override {
+ mContext =
+ test::ContextBuilder()
+ .setCompilationPackage("com.app.test")
+ .setNameManglerPolicy(
+ NameManglerPolicy{"com.app.test", {"com.android.support"}})
+ .addSymbolSource(
+ test::StaticSymbolSourceBuilder()
+ .addPublicSymbol(
+ "android:attr/layout_width", ResourceId(0x01010000),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_ENUM |
+ android::ResTable_map::TYPE_DIMENSION)
+ .addItem("match_parent", 0xffffffff)
+ .build())
+ .addPublicSymbol(
+ "android:attr/background", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_COLOR)
+ .build())
+ .addPublicSymbol("android:attr/attr",
+ ResourceId(0x01010002),
+ test::AttributeBuilder().build())
+ .addPublicSymbol(
+ "android:attr/text", ResourceId(0x01010003),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_STRING)
+ .build())
- // Add one real symbol that was introduces in v21
- .addPublicSymbol("android:attr/colorAccent", ResourceId(0x01010435),
- test::AttributeBuilder().build())
+ // Add one real symbol that was introduces in v21
+ .addPublicSymbol("android:attr/colorAccent",
+ ResourceId(0x01010435),
+ test::AttributeBuilder().build())
- // Private symbol.
- .addSymbol("android:color/hidden", ResourceId(0x01020001))
+ // Private symbol.
+ .addSymbol("android:color/hidden", ResourceId(0x01020001))
- .addPublicSymbol("android:id/id", ResourceId(0x01030000))
- .addSymbol("com.app.test:id/id", ResourceId(0x7f030000))
- .addSymbol("com.app.test:color/green", ResourceId(0x7f020000))
- .addSymbol("com.app.test:color/red", ResourceId(0x7f020001))
- .addSymbol("com.app.test:attr/colorAccent", ResourceId(0x7f010000),
- test::AttributeBuilder()
- .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
- .addPublicSymbol("com.app.test:attr/com.android.support$colorAccent",
- ResourceId(0x7f010001), test::AttributeBuilder()
- .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
- .addPublicSymbol("com.app.test:attr/attr", ResourceId(0x7f010002),
- test::AttributeBuilder().build())
- .build())
- .build();
- }
+ .addPublicSymbol("android:id/id", ResourceId(0x01030000))
+ .addSymbol("com.app.test:id/id", ResourceId(0x7f030000))
+ .addSymbol("com.app.test:color/green",
+ ResourceId(0x7f020000))
+ .addSymbol("com.app.test:color/red", ResourceId(0x7f020001))
+ .addSymbol(
+ "com.app.test:attr/colorAccent", ResourceId(0x7f010000),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_COLOR)
+ .build())
+ .addPublicSymbol(
+ "com.app.test:attr/com.android.support$colorAccent",
+ ResourceId(0x7f010001),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_COLOR)
+ .build())
+ .addPublicSymbol("com.app.test:attr/attr",
+ ResourceId(0x7f010002),
+ test::AttributeBuilder().build())
+ .build())
+ .build();
+ }
-protected:
- std::unique_ptr<IAaptContext> mContext;
+ protected:
+ std::unique_ptr<IAaptContext> mContext;
};
TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="@color/green"
android:text="hello"
class="hello" />)EOF");
- XmlReferenceLinker linker;
- ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- xml::Element* viewEl = xml::findRootElement(doc.get());
- ASSERT_NE(viewEl, nullptr);
+ xml::Element* viewEl = xml::findRootElement(doc.get());
+ ASSERT_NE(viewEl, nullptr);
- xml::Attribute* xmlAttr = viewEl->findAttribute(xml::kSchemaAndroid, "layout_width");
- ASSERT_NE(xmlAttr, nullptr);
- AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
- AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
- EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x01010000));
- ASSERT_NE(xmlAttr->compiledValue, nullptr);
- ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr);
+ xml::Attribute* xmlAttr =
+ viewEl->findAttribute(xml::kSchemaAndroid, "layout_width");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
+ EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(),
+ ResourceId(0x01010000));
+ ASSERT_NE(xmlAttr->compiledValue, nullptr);
+ ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr);
- xmlAttr = viewEl->findAttribute(xml::kSchemaAndroid, "background");
- ASSERT_NE(xmlAttr, nullptr);
- AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
- AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
- EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x01010001));
- ASSERT_NE(xmlAttr->compiledValue, nullptr);
- Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
- ASSERT_NE(ref, nullptr);
- AAPT_ASSERT_TRUE(ref->name);
- EXPECT_EQ(ref->name.value(), test::parseNameOrDie("color/green")); // Make sure the name
- // didn't change.
- AAPT_ASSERT_TRUE(ref->id);
- EXPECT_EQ(ref->id.value(), ResourceId(0x7f020000));
+ xmlAttr = viewEl->findAttribute(xml::kSchemaAndroid, "background");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
+ EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(),
+ ResourceId(0x01010001));
+ ASSERT_NE(xmlAttr->compiledValue, nullptr);
+ Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->name);
+ EXPECT_EQ(ref->name.value(),
+ test::parseNameOrDie("color/green")); // Make sure the name
+ // didn't change.
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x7f020000));
- xmlAttr = viewEl->findAttribute(xml::kSchemaAndroid, "text");
- ASSERT_NE(xmlAttr, nullptr);
- AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
- ASSERT_FALSE(xmlAttr->compiledValue); // Strings don't get compiled for memory sake.
+ xmlAttr = viewEl->findAttribute(xml::kSchemaAndroid, "text");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ ASSERT_FALSE(
+ xmlAttr->compiledValue); // Strings don't get compiled for memory sake.
- xmlAttr = viewEl->findAttribute("", "class");
- ASSERT_NE(xmlAttr, nullptr);
- AAPT_ASSERT_FALSE(xmlAttr->compiledAttribute);
- ASSERT_EQ(xmlAttr->compiledValue, nullptr);
+ xmlAttr = viewEl->findAttribute("", "class");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_FALSE(xmlAttr->compiledAttribute);
+ ASSERT_EQ(xmlAttr->compiledValue, nullptr);
}
TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:colorAccent="@android:color/hidden" />)EOF");
- XmlReferenceLinker linker;
- ASSERT_FALSE(linker.consume(mContext.get(), doc.get()));
+ XmlReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(mContext.get(), doc.get()));
}
-TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+TEST_F(XmlReferenceLinkerTest,
+ PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) {
+ std::unique_ptr<xml::XmlResource> doc =
+ test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:colorAccent="@*android:color/hidden" />)EOF");
- XmlReferenceLinker linker;
- ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
}
TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:colorAccent="#ffffff" />)EOF");
- XmlReferenceLinker linker;
- ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- EXPECT_TRUE(linker.getSdkLevels().count(21) == 1);
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+ EXPECT_TRUE(linker.getSdkLevels().count(21) == 1);
}
TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:support="http://schemas.android.com/apk/res/com.android.support"
support:colorAccent="#ff0000" />)EOF");
- XmlReferenceLinker linker;
- ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- xml::Element* viewEl = xml::findRootElement(doc.get());
- ASSERT_NE(viewEl, nullptr);
+ xml::Element* viewEl = xml::findRootElement(doc.get());
+ ASSERT_NE(viewEl, nullptr);
- xml::Attribute* xmlAttr = viewEl->findAttribute(
- xml::buildPackageNamespace("com.android.support"), "colorAccent");
- ASSERT_NE(xmlAttr, nullptr);
- AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
- AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
- EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010001));
- ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr);
+ xml::Attribute* xmlAttr = viewEl->findAttribute(
+ xml::buildPackageNamespace("com.android.support"), "colorAccent");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
+ EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(),
+ ResourceId(0x7f010001));
+ ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr);
}
TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:app="http://schemas.android.com/apk/res-auto"
app:colorAccent="@app:color/red" />)EOF");
- XmlReferenceLinker linker;
- ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- xml::Element* viewEl = xml::findRootElement(doc.get());
- ASSERT_NE(viewEl, nullptr);
+ xml::Element* viewEl = xml::findRootElement(doc.get());
+ ASSERT_NE(viewEl, nullptr);
- xml::Attribute* xmlAttr = viewEl->findAttribute(xml::kSchemaAuto, "colorAccent");
- ASSERT_NE(xmlAttr, nullptr);
- AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
- AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
- EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010000));
- Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
- ASSERT_NE(ref, nullptr);
- AAPT_ASSERT_TRUE(ref->name);
- AAPT_ASSERT_TRUE(ref->id);
- EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001));
+ xml::Attribute* xmlAttr =
+ viewEl->findAttribute(xml::kSchemaAuto, "colorAccent");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
+ EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(),
+ ResourceId(0x7f010000));
+ Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->name);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001));
}
TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:app="http://schemas.android.com/apk/res/android"
app:attr="@app:id/id">
<View xmlns:app="http://schemas.android.com/apk/res/com.app.test"
app:attr="@app:id/id"/>
</View>)EOF");
- XmlReferenceLinker linker;
- ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- xml::Element* viewEl = xml::findRootElement(doc.get());
- ASSERT_NE(viewEl, nullptr);
+ xml::Element* viewEl = xml::findRootElement(doc.get());
+ ASSERT_NE(viewEl, nullptr);
- // All attributes and references in this element should be referring to "android" (0x01).
- xml::Attribute* xmlAttr = viewEl->findAttribute(xml::kSchemaAndroid, "attr");
- ASSERT_NE(xmlAttr, nullptr);
- AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
- AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
- EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x01010002));
- Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
- ASSERT_NE(ref, nullptr);
- AAPT_ASSERT_TRUE(ref->id);
- EXPECT_EQ(ref->id.value(), ResourceId(0x01030000));
+ // All attributes and references in this element should be referring to
+ // "android" (0x01).
+ xml::Attribute* xmlAttr = viewEl->findAttribute(xml::kSchemaAndroid, "attr");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
+ EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(),
+ ResourceId(0x01010002));
+ Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x01030000));
- ASSERT_FALSE(viewEl->getChildElements().empty());
- viewEl = viewEl->getChildElements().front();
- ASSERT_NE(viewEl, nullptr);
+ ASSERT_FALSE(viewEl->getChildElements().empty());
+ viewEl = viewEl->getChildElements().front();
+ ASSERT_NE(viewEl, nullptr);
- // All attributes and references in this element should be referring to "com.app.test" (0x7f).
- xmlAttr = viewEl->findAttribute(xml::buildPackageNamespace("com.app.test"), "attr");
- ASSERT_NE(xmlAttr, nullptr);
- AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
- AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
- EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010002));
- ref = valueCast<Reference>(xmlAttr->compiledValue.get());
- ASSERT_NE(ref, nullptr);
- AAPT_ASSERT_TRUE(ref->id);
- EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000));
+ // All attributes and references in this element should be referring to
+ // "com.app.test" (0x7f).
+ xmlAttr =
+ viewEl->findAttribute(xml::buildPackageNamespace("com.app.test"), "attr");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
+ EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(),
+ ResourceId(0x7f010002));
+ ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000));
}
TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/com.app.test"
android:attr="@id/id"/>)EOF");
- XmlReferenceLinker linker;
- ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- xml::Element* viewEl = xml::findRootElement(doc.get());
- ASSERT_NE(viewEl, nullptr);
+ xml::Element* viewEl = xml::findRootElement(doc.get());
+ ASSERT_NE(viewEl, nullptr);
- // All attributes and references in this element should be referring to "com.app.test" (0x7f).
- xml::Attribute* xmlAttr = viewEl->findAttribute(xml::buildPackageNamespace("com.app.test"),
- "attr");
- ASSERT_NE(xmlAttr, nullptr);
- AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
- AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
- EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010002));
- Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
- ASSERT_NE(ref, nullptr);
- AAPT_ASSERT_TRUE(ref->id);
- EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000));
+ // All attributes and references in this element should be referring to
+ // "com.app.test" (0x7f).
+ xml::Attribute* xmlAttr =
+ viewEl->findAttribute(xml::buildPackageNamespace("com.app.test"), "attr");
+ ASSERT_NE(xmlAttr, nullptr);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute);
+ AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id);
+ EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(),
+ ResourceId(0x7f010002));
+ Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
+ ASSERT_NE(ref, nullptr);
+ AAPT_ASSERT_TRUE(ref->id);
+ EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h
index a7e752a..a9bde39 100644
--- a/tools/aapt2/process/IResourceTableConsumer.h
+++ b/tools/aapt2/process/IResourceTableConsumer.h
@@ -33,21 +33,21 @@
class SymbolTable;
struct IAaptContext {
- virtual ~IAaptContext() = default;
+ virtual ~IAaptContext() = default;
- virtual SymbolTable* getExternalSymbols() = 0;
- virtual IDiagnostics* getDiagnostics() = 0;
- virtual const std::string& getCompilationPackage() = 0;
- virtual uint8_t getPackageId() = 0;
- virtual NameMangler* getNameMangler() = 0;
- virtual bool verbose() = 0;
- virtual int getMinSdkVersion() = 0;
+ virtual SymbolTable* getExternalSymbols() = 0;
+ virtual IDiagnostics* getDiagnostics() = 0;
+ virtual const std::string& getCompilationPackage() = 0;
+ virtual uint8_t getPackageId() = 0;
+ virtual NameMangler* getNameMangler() = 0;
+ virtual bool verbose() = 0;
+ virtual int getMinSdkVersion() = 0;
};
struct IResourceTableConsumer {
- virtual ~IResourceTableConsumer() = default;
+ virtual ~IResourceTableConsumer() = default;
- virtual bool consume(IAaptContext* context, ResourceTable* table) = 0;
+ virtual bool consume(IAaptContext* context, ResourceTable* table) = 0;
};
namespace xml {
@@ -55,11 +55,11 @@
}
struct IXmlResourceConsumer {
- virtual ~IXmlResourceConsumer() = default;
+ virtual ~IXmlResourceConsumer() = default;
- virtual bool consume(IAaptContext* context, xml::XmlResource* resource) = 0;
+ virtual bool consume(IAaptContext* context, xml::XmlResource* resource) = 0;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_PROCESS_IRESOURCETABLECONSUMER_H */
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 0c92718..00ffeb2 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -14,11 +14,11 @@
* limitations under the License.
*/
+#include "process/SymbolTable.h"
#include "ConfigDescription.h"
#include "Resource.h"
#include "ResourceUtils.h"
#include "ValueVisitor.h"
-#include "process/SymbolTable.h"
#include "util/Util.h"
#include <androidfw/AssetManager.h>
@@ -27,254 +27,273 @@
namespace aapt {
void SymbolTable::appendSource(std::unique_ptr<ISymbolSource> source) {
- mSources.push_back(std::move(source));
+ mSources.push_back(std::move(source));
- // We do not clear the cache, because sources earlier in the list take precedent.
+ // We do not clear the cache, because sources earlier in the list take
+ // precedent.
}
void SymbolTable::prependSource(std::unique_ptr<ISymbolSource> source) {
- mSources.insert(mSources.begin(), std::move(source));
+ mSources.insert(mSources.begin(), std::move(source));
- // We must clear the cache in case we did a lookup before adding this resource.
- mCache.clear();
+ // We must clear the cache in case we did a lookup before adding this
+ // resource.
+ mCache.clear();
}
const SymbolTable::Symbol* SymbolTable::findByName(const ResourceName& name) {
- if (const std::shared_ptr<Symbol>& s = mCache.get(name)) {
- return s.get();
- }
+ if (const std::shared_ptr<Symbol>& s = mCache.get(name)) {
+ return s.get();
+ }
- // We did not find it in the cache, so look through the sources.
- for (auto& symbolSource : mSources) {
- std::unique_ptr<Symbol> symbol = symbolSource->findByName(name);
- if (symbol) {
- // Take ownership of the symbol into a shared_ptr. We do this because LruCache
- // doesn't support unique_ptr.
- std::shared_ptr<Symbol> sharedSymbol = std::shared_ptr<Symbol>(symbol.release());
- mCache.put(name, sharedSymbol);
+ // We did not find it in the cache, so look through the sources.
+ for (auto& symbolSource : mSources) {
+ std::unique_ptr<Symbol> symbol = symbolSource->findByName(name);
+ if (symbol) {
+ // Take ownership of the symbol into a shared_ptr. We do this because
+ // LruCache
+ // doesn't support unique_ptr.
+ std::shared_ptr<Symbol> sharedSymbol =
+ std::shared_ptr<Symbol>(symbol.release());
+ mCache.put(name, sharedSymbol);
- if (sharedSymbol->id) {
- // The symbol has an ID, so we can also cache this!
- mIdCache.put(sharedSymbol->id.value(), sharedSymbol);
- }
- return sharedSymbol.get();
- }
+ if (sharedSymbol->id) {
+ // The symbol has an ID, so we can also cache this!
+ mIdCache.put(sharedSymbol->id.value(), sharedSymbol);
+ }
+ return sharedSymbol.get();
}
- return nullptr;
+ }
+ return nullptr;
}
const SymbolTable::Symbol* SymbolTable::findById(const ResourceId& id) {
- if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) {
- return s.get();
- }
+ if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) {
+ return s.get();
+ }
- // We did not find it in the cache, so look through the sources.
- for (auto& symbolSource : mSources) {
- std::unique_ptr<Symbol> symbol = symbolSource->findById(id);
- if (symbol) {
- // Take ownership of the symbol into a shared_ptr. We do this because LruCache
- // doesn't support unique_ptr.
- std::shared_ptr<Symbol> sharedSymbol = std::shared_ptr<Symbol>(symbol.release());
- mIdCache.put(id, sharedSymbol);
- return sharedSymbol.get();
- }
+ // We did not find it in the cache, so look through the sources.
+ for (auto& symbolSource : mSources) {
+ std::unique_ptr<Symbol> symbol = symbolSource->findById(id);
+ if (symbol) {
+ // Take ownership of the symbol into a shared_ptr. We do this because
+ // LruCache
+ // doesn't support unique_ptr.
+ std::shared_ptr<Symbol> sharedSymbol =
+ std::shared_ptr<Symbol>(symbol.release());
+ mIdCache.put(id, sharedSymbol);
+ return sharedSymbol.get();
}
- return nullptr;
+ }
+ return nullptr;
}
const SymbolTable::Symbol* SymbolTable::findByReference(const Reference& ref) {
- // First try the ID. This is because when we lookup by ID, we only fill in the ID cache.
- // Looking up by name fills in the name and ID cache. So a cache miss will cause a failed
- // ID lookup, then a successful name lookup. Subsequent look ups will hit immediately
- // because the ID is cached too.
- //
- // If we looked up by name first, a cache miss would mean we failed to lookup by name, then
- // succeeded to lookup by ID. Subsequent lookups will miss then hit.
- const SymbolTable::Symbol* symbol = nullptr;
- if (ref.id) {
- symbol = findById(ref.id.value());
- }
+ // First try the ID. This is because when we lookup by ID, we only fill in the
+ // ID cache.
+ // Looking up by name fills in the name and ID cache. So a cache miss will
+ // cause a failed
+ // ID lookup, then a successful name lookup. Subsequent look ups will hit
+ // immediately
+ // because the ID is cached too.
+ //
+ // If we looked up by name first, a cache miss would mean we failed to lookup
+ // by name, then
+ // succeeded to lookup by ID. Subsequent lookups will miss then hit.
+ const SymbolTable::Symbol* symbol = nullptr;
+ if (ref.id) {
+ symbol = findById(ref.id.value());
+ }
- if (ref.name && !symbol) {
- symbol = findByName(ref.name.value());
- }
- return symbol;
+ if (ref.name && !symbol) {
+ symbol = findByName(ref.name.value());
+ }
+ return symbol;
}
std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::findByName(
- const ResourceName& name) {
- Maybe<ResourceTable::SearchResult> result = mTable->findResource(name);
- if (!result) {
- if (name.type == ResourceType::kAttr) {
- // Recurse and try looking up a private attribute.
- return findByName(ResourceName(name.package, ResourceType::kAttrPrivate, name.entry));
- }
+ const ResourceName& name) {
+ Maybe<ResourceTable::SearchResult> result = mTable->findResource(name);
+ if (!result) {
+ if (name.type == ResourceType::kAttr) {
+ // Recurse and try looking up a private attribute.
+ return findByName(
+ ResourceName(name.package, ResourceType::kAttrPrivate, name.entry));
+ }
+ return {};
+ }
+
+ ResourceTable::SearchResult sr = result.value();
+
+ std::unique_ptr<SymbolTable::Symbol> symbol =
+ util::make_unique<SymbolTable::Symbol>();
+ symbol->isPublic = (sr.entry->symbolStatus.state == SymbolState::kPublic);
+
+ if (sr.package->id && sr.type->id && sr.entry->id) {
+ symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(),
+ sr.entry->id.value());
+ }
+
+ if (name.type == ResourceType::kAttr ||
+ name.type == ResourceType::kAttrPrivate) {
+ const ConfigDescription kDefaultConfig;
+ ResourceConfigValue* configValue = sr.entry->findValue(kDefaultConfig);
+ if (configValue) {
+ // This resource has an Attribute.
+ if (Attribute* attr = valueCast<Attribute>(configValue->value.get())) {
+ symbol->attribute = std::make_shared<Attribute>(*attr);
+ } else {
return {};
+ }
}
-
- ResourceTable::SearchResult sr = result.value();
-
- std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>();
- symbol->isPublic = (sr.entry->symbolStatus.state == SymbolState::kPublic);
-
- if (sr.package->id && sr.type->id && sr.entry->id) {
- symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
- }
-
- if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
- const ConfigDescription kDefaultConfig;
- ResourceConfigValue* configValue = sr.entry->findValue(kDefaultConfig);
- if (configValue) {
- // This resource has an Attribute.
- if (Attribute* attr = valueCast<Attribute>(configValue->value.get())) {
- symbol->attribute = std::make_shared<Attribute>(*attr);
- } else {
- return {};
- }
- }
- }
- return symbol;
+ }
+ return symbol;
}
bool AssetManagerSymbolSource::addAssetPath(const StringPiece& path) {
- int32_t cookie = 0;
- return mAssets.addAssetPath(android::String8(path.data(), path.size()), &cookie);
+ int32_t cookie = 0;
+ return mAssets.addAssetPath(android::String8(path.data(), path.size()),
+ &cookie);
}
-static std::unique_ptr<SymbolTable::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);
- if (count < 0) {
- table.unlockBag(entry);
- return nullptr;
+static std::unique_ptr<SymbolTable::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);
+ if (count < 0) {
+ table.unlockBag(entry);
+ return nullptr;
+ }
+
+ // We found a resource.
+ std::unique_ptr<SymbolTable::Symbol> s =
+ util::make_unique<SymbolTable::Symbol>();
+ s->id = id;
+
+ // Check to see if it is an attribute.
+ for (size_t i = 0; i < (size_t)count; i++) {
+ if (entry[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
+ s->attribute = std::make_shared<Attribute>(false);
+ s->attribute->typeMask = entry[i].map.value.data;
+ break;
}
+ }
- // We found a resource.
- std::unique_ptr<SymbolTable::Symbol> s = util::make_unique<SymbolTable::Symbol>();
- s->id = id;
-
- // Check to see if it is an attribute.
- for (size_t i = 0; i < (size_t) count; i++) {
- if (entry[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
- s->attribute = std::make_shared<Attribute>(false);
- s->attribute->typeMask = entry[i].map.value.data;
+ if (s->attribute) {
+ for (size_t i = 0; i < (size_t)count; i++) {
+ const android::ResTable_map& mapEntry = entry[i].map;
+ if (Res_INTERNALID(mapEntry.name.ident)) {
+ switch (mapEntry.name.ident) {
+ case android::ResTable_map::ATTR_MIN:
+ s->attribute->minInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
+ case android::ResTable_map::ATTR_MAX:
+ s->attribute->maxInt = static_cast<int32_t>(mapEntry.value.data);
break;
}
+ continue;
+ }
+
+ android::ResTable::resource_name entryName;
+ if (!table.getResourceName(mapEntry.name.ident, false, &entryName)) {
+ table.unlockBag(entry);
+ return nullptr;
+ }
+
+ Maybe<ResourceName> parsedName = ResourceUtils::toResourceName(entryName);
+ if (!parsedName) {
+ return nullptr;
+ }
+
+ Attribute::Symbol symbol;
+ symbol.symbol.name = parsedName.value();
+ symbol.symbol.id = ResourceId(mapEntry.name.ident);
+ symbol.value = mapEntry.value.data;
+ s->attribute->symbols.push_back(std::move(symbol));
}
-
- if (s->attribute) {
- for (size_t i = 0; i < (size_t) count; i++) {
- const android::ResTable_map& mapEntry = entry[i].map;
- if (Res_INTERNALID(mapEntry.name.ident)) {
- switch (mapEntry.name.ident) {
- case android::ResTable_map::ATTR_MIN:
- s->attribute->minInt = static_cast<int32_t>(mapEntry.value.data);
- break;
- case android::ResTable_map::ATTR_MAX:
- s->attribute->maxInt = static_cast<int32_t>(mapEntry.value.data);
- break;
- }
- continue;
- }
-
- android::ResTable::resource_name entryName;
- if (!table.getResourceName(mapEntry.name.ident, false, &entryName)) {
- table.unlockBag(entry);
- return nullptr;
- }
-
- Maybe<ResourceName> parsedName = ResourceUtils::toResourceName(entryName);
- if (!parsedName) {
- return nullptr;
- }
-
- Attribute::Symbol symbol;
- symbol.symbol.name = parsedName.value();
- symbol.symbol.id = ResourceId(mapEntry.name.ident);
- symbol.value = mapEntry.value.data;
- s->attribute->symbols.push_back(std::move(symbol));
- }
- }
- table.unlockBag(entry);
- return s;
+ }
+ table.unlockBag(entry);
+ return s;
}
std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findByName(
- const ResourceName& name) {
- const android::ResTable& table = mAssets.getResources(false);
+ const ResourceName& name) {
+ const android::ResTable& table = mAssets.getResources(false);
- const std::u16string package16 = util::utf8ToUtf16(name.package);
- const std::u16string type16 = util::utf8ToUtf16(toString(name.type));
- const std::u16string entry16 = util::utf8ToUtf16(name.entry);
+ const std::u16string package16 = util::utf8ToUtf16(name.package);
+ const std::u16string type16 = util::utf8ToUtf16(toString(name.type));
+ const std::u16string entry16 = util::utf8ToUtf16(name.entry);
- uint32_t typeSpecFlags = 0;
- ResourceId resId = table.identifierForName(entry16.data(), entry16.size(),
- type16.data(), type16.size(),
- package16.data(), package16.size(),
- &typeSpecFlags);
- if (!resId.isValid()) {
- return {};
- }
-
- std::unique_ptr<SymbolTable::Symbol> s;
- if (name.type == ResourceType::kAttr) {
- s = lookupAttributeInTable(table, resId);
- } else {
- s = util::make_unique<SymbolTable::Symbol>();
- s->id = resId;
- }
-
- if (s) {
- s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
- return s;
- }
+ uint32_t typeSpecFlags = 0;
+ ResourceId resId = table.identifierForName(
+ entry16.data(), entry16.size(), type16.data(), type16.size(),
+ package16.data(), package16.size(), &typeSpecFlags);
+ if (!resId.isValid()) {
return {};
+ }
+
+ std::unique_ptr<SymbolTable::Symbol> s;
+ if (name.type == ResourceType::kAttr) {
+ s = lookupAttributeInTable(table, resId);
+ } else {
+ s = util::make_unique<SymbolTable::Symbol>();
+ s->id = resId;
+ }
+
+ if (s) {
+ s->isPublic =
+ (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
+ return s;
+ }
+ return {};
}
-static Maybe<ResourceName> getResourceName(const android::ResTable& table, ResourceId id) {
- android::ResTable::resource_name resName = {};
- if (!table.getResourceName(id.id, true, &resName)) {
- return {};
- }
- return ResourceUtils::toResourceName(resName);
+static Maybe<ResourceName> getResourceName(const android::ResTable& table,
+ ResourceId id) {
+ android::ResTable::resource_name resName = {};
+ if (!table.getResourceName(id.id, true, &resName)) {
+ return {};
+ }
+ return ResourceUtils::toResourceName(resName);
}
-std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findById(ResourceId id) {
- const android::ResTable& table = mAssets.getResources(false);
- Maybe<ResourceName> maybeName = getResourceName(table, id);
- if (!maybeName) {
- return {};
- }
-
- uint32_t typeSpecFlags = 0;
- table.getResourceFlags(id.id, &typeSpecFlags);
-
- std::unique_ptr<SymbolTable::Symbol> s;
- if (maybeName.value().type == ResourceType::kAttr) {
- s = lookupAttributeInTable(table, id);
- } else {
- s = util::make_unique<SymbolTable::Symbol>();
- s->id = id;
- }
-
- if (s) {
- s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
- return s;
- }
+std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findById(
+ ResourceId id) {
+ const android::ResTable& table = mAssets.getResources(false);
+ Maybe<ResourceName> maybeName = getResourceName(table, id);
+ if (!maybeName) {
return {};
+ }
+
+ uint32_t typeSpecFlags = 0;
+ table.getResourceFlags(id.id, &typeSpecFlags);
+
+ std::unique_ptr<SymbolTable::Symbol> s;
+ if (maybeName.value().type == ResourceType::kAttr) {
+ s = lookupAttributeInTable(table, id);
+ } else {
+ s = util::make_unique<SymbolTable::Symbol>();
+ s->id = id;
+ }
+
+ if (s) {
+ s->isPublic =
+ (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
+ return s;
+ }
+ return {};
}
std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findByReference(
- const Reference& ref) {
- // AssetManager always prefers IDs.
- if (ref.id) {
- return findById(ref.id.value());
- } else if (ref.name) {
- return findByName(ref.name.value());
- }
- return {};
+ const Reference& ref) {
+ // AssetManager always prefers IDs.
+ if (ref.id) {
+ return findById(ref.id.value());
+ } else if (ref.name) {
+ return findByName(ref.name.value());
+ }
+ return {};
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index bd31416..89d87de 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -34,99 +34,100 @@
namespace aapt {
inline android::hash_t hash_type(const ResourceName& name) {
- std::hash<std::string> strHash;
- android::hash_t hash = 0;
- hash = android::JenkinsHashMix(hash, (uint32_t) strHash(name.package));
- hash = android::JenkinsHashMix(hash, (uint32_t) name.type);
- hash = android::JenkinsHashMix(hash, (uint32_t) strHash(name.entry));
- return hash;
+ std::hash<std::string> strHash;
+ android::hash_t hash = 0;
+ hash = android::JenkinsHashMix(hash, (uint32_t)strHash(name.package));
+ hash = android::JenkinsHashMix(hash, (uint32_t)name.type);
+ hash = android::JenkinsHashMix(hash, (uint32_t)strHash(name.entry));
+ return hash;
}
inline android::hash_t hash_type(const ResourceId& id) {
- return android::hash_type(id.id);
+ return android::hash_type(id.id);
}
class ISymbolSource;
class SymbolTable {
-public:
- struct Symbol {
- Symbol() : Symbol(Maybe<ResourceId>{}) {
- }
+ public:
+ struct Symbol {
+ Symbol() : Symbol(Maybe<ResourceId>{}) {}
- explicit Symbol(const Maybe<ResourceId>& i) : Symbol(i, nullptr) {
- }
+ explicit Symbol(const Maybe<ResourceId>& i) : Symbol(i, nullptr) {}
- Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr) :
- Symbol(i, attr, false) {
- }
+ Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr)
+ : Symbol(i, attr, false) {}
- Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr, bool pub) :
- id(i), attribute(attr), isPublic(pub) {
- }
+ Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr,
+ bool pub)
+ : id(i), attribute(attr), isPublic(pub) {}
- Symbol(const Symbol&) = default;
- Symbol(Symbol&&) = default;
- Symbol& operator=(const Symbol&) = default;
- Symbol& operator=(Symbol&&) = default;
+ Symbol(const Symbol&) = default;
+ Symbol(Symbol&&) = default;
+ Symbol& operator=(const Symbol&) = default;
+ Symbol& operator=(Symbol&&) = default;
- Maybe<ResourceId> id;
- std::shared_ptr<Attribute> attribute;
- bool isPublic = false;
- };
+ Maybe<ResourceId> id;
+ std::shared_ptr<Attribute> attribute;
+ bool isPublic = false;
+ };
- SymbolTable() : mCache(200), mIdCache(200) {
- }
+ SymbolTable() : mCache(200), mIdCache(200) {}
- void appendSource(std::unique_ptr<ISymbolSource> source);
- void prependSource(std::unique_ptr<ISymbolSource> source);
+ void appendSource(std::unique_ptr<ISymbolSource> source);
+ void prependSource(std::unique_ptr<ISymbolSource> source);
- /**
- * Never hold on to the result between calls to findByName or findById. The results
- * are typically stored in a cache which may evict entries.
- */
- const Symbol* findByName(const ResourceName& name);
- const Symbol* findById(const ResourceId& id);
+ /**
+ * Never hold on to the result between calls to findByName or findById. The
+ * results
+ * are typically stored in a cache which may evict entries.
+ */
+ const Symbol* findByName(const ResourceName& name);
+ const Symbol* findById(const ResourceId& id);
- /**
- * Let's the ISymbolSource decide whether looking up by name or ID is faster, if both
- * are available.
- */
- const Symbol* findByReference(const Reference& ref);
+ /**
+ * Let's the ISymbolSource decide whether looking up by name or ID is faster,
+ * if both
+ * are available.
+ */
+ const Symbol* findByReference(const Reference& ref);
-private:
- std::vector<std::unique_ptr<ISymbolSource>> mSources;
+ private:
+ std::vector<std::unique_ptr<ISymbolSource>> mSources;
- // We use shared_ptr because unique_ptr is not supported and
- // we need automatic deletion.
- android::LruCache<ResourceName, std::shared_ptr<Symbol>> mCache;
- android::LruCache<ResourceId, std::shared_ptr<Symbol>> mIdCache;
+ // We use shared_ptr because unique_ptr is not supported and
+ // we need automatic deletion.
+ android::LruCache<ResourceName, std::shared_ptr<Symbol>> mCache;
+ android::LruCache<ResourceId, std::shared_ptr<Symbol>> mIdCache;
- DISALLOW_COPY_AND_ASSIGN(SymbolTable);
+ DISALLOW_COPY_AND_ASSIGN(SymbolTable);
};
/**
- * An interface that a symbol source implements in order to surface symbol information
+ * An interface that a symbol source implements in order to surface symbol
+ * information
* to the symbol table.
*/
class ISymbolSource {
-public:
- virtual ~ISymbolSource() = default;
+ public:
+ virtual ~ISymbolSource() = default;
- virtual std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) = 0;
- virtual std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) = 0;
+ virtual std::unique_ptr<SymbolTable::Symbol> findByName(
+ const ResourceName& name) = 0;
+ virtual std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) = 0;
- /**
- * Default implementation tries the name if it exists, else the ID.
- */
- virtual std::unique_ptr<SymbolTable::Symbol> findByReference(const Reference& ref) {
- if (ref.name) {
- return findByName(ref.name.value());
- } else if (ref.id) {
- return findById(ref.id.value());
- }
- return {};
+ /**
+ * Default implementation tries the name if it exists, else the ID.
+ */
+ virtual std::unique_ptr<SymbolTable::Symbol> findByReference(
+ const Reference& ref) {
+ if (ref.name) {
+ return findByName(ref.name.value());
+ } else if (ref.id) {
+ return findById(ref.id.value());
}
+ return {};
+ }
};
/**
@@ -135,38 +136,40 @@
* Lookups by ID are ignored.
*/
class ResourceTableSymbolSource : public ISymbolSource {
-public:
- explicit ResourceTableSymbolSource(ResourceTable* table) : mTable(table) {
- }
+ public:
+ explicit ResourceTableSymbolSource(ResourceTable* table) : mTable(table) {}
- std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) override;
+ std::unique_ptr<SymbolTable::Symbol> findByName(
+ const ResourceName& name) override;
- std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override {
- return {};
- }
+ std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override {
+ return {};
+ }
-private:
- ResourceTable* mTable;
+ private:
+ ResourceTable* mTable;
- DISALLOW_COPY_AND_ASSIGN(ResourceTableSymbolSource);
+ DISALLOW_COPY_AND_ASSIGN(ResourceTableSymbolSource);
};
class AssetManagerSymbolSource : public ISymbolSource {
-public:
- AssetManagerSymbolSource() = default;
+ public:
+ AssetManagerSymbolSource() = default;
- bool addAssetPath(const StringPiece& path);
+ bool addAssetPath(const StringPiece& path);
- std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) override;
- std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override;
- std::unique_ptr<SymbolTable::Symbol> findByReference(const Reference& ref) override;
+ std::unique_ptr<SymbolTable::Symbol> findByName(
+ const ResourceName& name) override;
+ std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override;
+ std::unique_ptr<SymbolTable::Symbol> findByReference(
+ const Reference& ref) override;
-private:
- android::AssetManager mAssets;
+ private:
+ android::AssetManager mAssets;
- DISALLOW_COPY_AND_ASSIGN(AssetManagerSymbolSource);
+ DISALLOW_COPY_AND_ASSIGN(AssetManagerSymbolSource);
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_PROCESS_SYMBOLTABLE_H */
diff --git a/tools/aapt2/process/SymbolTable_test.cpp b/tools/aapt2/process/SymbolTable_test.cpp
index 1626352..00474f7 100644
--- a/tools/aapt2/process/SymbolTable_test.cpp
+++ b/tools/aapt2/process/SymbolTable_test.cpp
@@ -20,34 +20,38 @@
namespace aapt {
TEST(ResourceTableSymbolSourceTest, FindSymbols) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addSimple("android:id/foo", ResourceId(0x01020000))
- .addSimple("android:id/bar")
- .addValue("android:attr/foo", ResourceId(0x01010000),
- test::AttributeBuilder().build())
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .addSimple("android:id/foo", ResourceId(0x01020000))
+ .addSimple("android:id/bar")
+ .addValue("android:attr/foo", ResourceId(0x01010000),
+ test::AttributeBuilder().build())
+ .build();
- ResourceTableSymbolSource symbolSource(table.get());
- EXPECT_NE(nullptr, symbolSource.findByName(test::parseNameOrDie("android:id/foo")));
- EXPECT_NE(nullptr, symbolSource.findByName(test::parseNameOrDie("android:id/bar")));
+ ResourceTableSymbolSource symbolSource(table.get());
+ EXPECT_NE(nullptr,
+ symbolSource.findByName(test::parseNameOrDie("android:id/foo")));
+ EXPECT_NE(nullptr,
+ symbolSource.findByName(test::parseNameOrDie("android:id/bar")));
- std::unique_ptr<SymbolTable::Symbol> s = symbolSource.findByName(
- test::parseNameOrDie("android:attr/foo"));
- ASSERT_NE(nullptr, s);
- EXPECT_NE(nullptr, s->attribute);
+ std::unique_ptr<SymbolTable::Symbol> s =
+ symbolSource.findByName(test::parseNameOrDie("android:attr/foo"));
+ ASSERT_NE(nullptr, s);
+ EXPECT_NE(nullptr, s->attribute);
}
TEST(ResourceTableSymbolSourceTest, FindPrivateAttrSymbol) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addValue("android:^attr-private/foo", ResourceId(0x01010000),
- test::AttributeBuilder().build())
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .addValue("android:^attr-private/foo", ResourceId(0x01010000),
+ test::AttributeBuilder().build())
+ .build();
- ResourceTableSymbolSource symbolSource(table.get());
- std::unique_ptr<SymbolTable::Symbol> s = symbolSource.findByName(
- test::parseNameOrDie("android:attr/foo"));
- ASSERT_NE(nullptr, s);
- EXPECT_NE(nullptr, s->attribute);
+ ResourceTableSymbolSource symbolSource(table.get());
+ std::unique_ptr<SymbolTable::Symbol> s =
+ symbolSource.findByName(test::parseNameOrDie("android:attr/foo"));
+ ASSERT_NE(nullptr, s);
+ EXPECT_NE(nullptr, s->attribute);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/proto/ProtoHelpers.h b/tools/aapt2/proto/ProtoHelpers.h
index 02e67f1..7271e8b 100644
--- a/tools/aapt2/proto/ProtoHelpers.h
+++ b/tools/aapt2/proto/ProtoHelpers.h
@@ -30,14 +30,18 @@
void serializeStringPoolToPb(const StringPool& pool, pb::StringPool* outPbPool);
-void serializeSourceToPb(const Source& source, StringPool* srcPool, pb::Source* outPbSource);
-void deserializeSourceFromPb(const pb::Source& pbSource, const android::ResStringPool& srcPool,
+void serializeSourceToPb(const Source& source, StringPool* srcPool,
+ pb::Source* outPbSource);
+void deserializeSourceFromPb(const pb::Source& pbSource,
+ const android::ResStringPool& srcPool,
Source* outSource);
pb::SymbolStatus_Visibility serializeVisibilityToPb(SymbolState state);
-SymbolState deserializeVisibilityFromPb(pb::SymbolStatus_Visibility pbVisibility);
+SymbolState deserializeVisibilityFromPb(
+ pb::SymbolStatus_Visibility pbVisibility);
-void serializeConfig(const ConfigDescription& config, pb::ConfigDescription* outPbConfig);
+void serializeConfig(const ConfigDescription& config,
+ pb::ConfigDescription* outPbConfig);
bool deserializeConfigDescriptionFromPb(const pb::ConfigDescription& pbConfig,
ConfigDescription* outConfig);
@@ -47,6 +51,6 @@
pb::Plural_Arity serializePluralEnumToPb(size_t pluralIdx);
size_t deserializePluralEnumFromPb(pb::Plural_Arity arity);
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_PROTO_PROTOHELPERS_H */
diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h
index cc7c251..378dafd 100644
--- a/tools/aapt2/proto/ProtoSerialize.h
+++ b/tools/aapt2/proto/ProtoSerialize.h
@@ -29,49 +29,49 @@
namespace aapt {
class CompiledFileOutputStream {
-public:
- explicit CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out);
+ public:
+ explicit CompiledFileOutputStream(
+ google::protobuf::io::ZeroCopyOutputStream* out);
- void WriteLittleEndian32(uint32_t value);
- void WriteCompiledFile(const pb::CompiledFile* compiledFile);
- void WriteData(const BigBuffer* buffer);
- void WriteData(const void* data, size_t len);
- bool HadError();
+ void WriteLittleEndian32(uint32_t value);
+ void WriteCompiledFile(const pb::CompiledFile* compiledFile);
+ void WriteData(const BigBuffer* buffer);
+ void WriteData(const void* data, size_t len);
+ bool HadError();
-private:
- DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream);
- void ensureAlignedWrite();
+ void ensureAlignedWrite();
- google::protobuf::io::CodedOutputStream mOut;
+ google::protobuf::io::CodedOutputStream mOut;
};
class CompiledFileInputStream {
-public:
- explicit CompiledFileInputStream(const void* data, size_t size);
+ public:
+ explicit CompiledFileInputStream(const void* data, size_t size);
- bool ReadLittleEndian32(uint32_t* outVal);
- bool ReadCompiledFile(pb::CompiledFile* outVal);
- bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen);
+ bool ReadLittleEndian32(uint32_t* outVal);
+ bool ReadCompiledFile(pb::CompiledFile* outVal);
+ bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen);
-private:
- DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream);
- void ensureAlignedRead();
+ void ensureAlignedRead();
- google::protobuf::io::CodedInputStream mIn;
+ google::protobuf::io::CodedInputStream mIn;
};
std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table);
-std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable,
- const Source& source,
- IDiagnostics* diag);
+std::unique_ptr<ResourceTable> deserializeTableFromPb(
+ const pb::ResourceTable& pbTable, const Source& source, IDiagnostics* diag);
-std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& file);
-std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(const pb::CompiledFile& pbFile,
- const Source& source,
- IDiagnostics* diag);
+std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(
+ const ResourceFile& file);
+std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(
+ const pb::CompiledFile& pbFile, const Source& source, IDiagnostics* diag);
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_FLATTEN_TABLEPROTOSERIALIZER_H */
diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp
index 595fa6f..0dfb01c 100644
--- a/tools/aapt2/proto/TableProtoDeserializer.cpp
+++ b/tools/aapt2/proto/TableProtoDeserializer.cpp
@@ -27,445 +27,460 @@
namespace {
class ReferenceIdToNameVisitor : public ValueVisitor {
-public:
- using ValueVisitor::visit;
+ public:
+ using ValueVisitor::visit;
- explicit ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceNameRef>* mapping) :
- mMapping(mapping) {
- assert(mMapping);
+ explicit ReferenceIdToNameVisitor(
+ const std::map<ResourceId, ResourceNameRef>* mapping)
+ : mMapping(mapping) {
+ assert(mMapping);
+ }
+
+ void visit(Reference* reference) override {
+ if (!reference->id || !reference->id.value().isValid()) {
+ return;
}
- void visit(Reference* reference) override {
- if (!reference->id || !reference->id.value().isValid()) {
- return;
- }
-
- ResourceId id = reference->id.value();
- auto cacheIter = mMapping->find(id);
- if (cacheIter != mMapping->end()) {
- reference->name = cacheIter->second.toResourceName();
- }
+ ResourceId id = reference->id.value();
+ auto cacheIter = mMapping->find(id);
+ if (cacheIter != mMapping->end()) {
+ reference->name = cacheIter->second.toResourceName();
}
+ }
-private:
- const std::map<ResourceId, ResourceNameRef>* mMapping;
+ private:
+ const std::map<ResourceId, ResourceNameRef>* mMapping;
};
class PackagePbDeserializer {
-public:
- PackagePbDeserializer(const android::ResStringPool* valuePool,
- const android::ResStringPool* sourcePool,
- const android::ResStringPool* symbolPool,
- const Source& source, IDiagnostics* diag) :
- mValuePool(valuePool), mSourcePool(sourcePool), mSymbolPool(symbolPool),
- mSource(source), mDiag(diag) {
+ public:
+ PackagePbDeserializer(const android::ResStringPool* valuePool,
+ const android::ResStringPool* sourcePool,
+ const android::ResStringPool* symbolPool,
+ const Source& source, IDiagnostics* diag)
+ : mValuePool(valuePool),
+ mSourcePool(sourcePool),
+ mSymbolPool(symbolPool),
+ mSource(source),
+ mDiag(diag) {}
+
+ public:
+ bool deserializeFromPb(const pb::Package& pbPackage, ResourceTable* table) {
+ Maybe<uint8_t> id;
+ if (pbPackage.has_package_id()) {
+ id = static_cast<uint8_t>(pbPackage.package_id());
}
-public:
- bool deserializeFromPb(const pb::Package& pbPackage, ResourceTable* table) {
- Maybe<uint8_t> id;
- if (pbPackage.has_package_id()) {
- id = static_cast<uint8_t>(pbPackage.package_id());
- }
+ std::map<ResourceId, ResourceNameRef> idIndex;
- std::map<ResourceId, ResourceNameRef> idIndex;
+ ResourceTablePackage* pkg =
+ table->createPackage(pbPackage.package_name(), id);
+ for (const pb::Type& pbType : pbPackage.types()) {
+ const ResourceType* resType = parseResourceType(pbType.name());
+ if (!resType) {
+ mDiag->error(DiagMessage(mSource) << "unknown type '" << pbType.name()
+ << "'");
+ return {};
+ }
- ResourceTablePackage* pkg = table->createPackage(pbPackage.package_name(), id);
- for (const pb::Type& pbType : pbPackage.types()) {
- const ResourceType* resType = parseResourceType(pbType.name());
- if (!resType) {
- mDiag->error(DiagMessage(mSource) << "unknown type '" << pbType.name() << "'");
- return {};
+ ResourceTableType* type = pkg->findOrCreateType(*resType);
+
+ for (const pb::Entry& pbEntry : pbType.entries()) {
+ ResourceEntry* entry = type->findOrCreateEntry(pbEntry.name());
+
+ // Deserialize the symbol status (public/private with source and
+ // comments).
+ if (pbEntry.has_symbol_status()) {
+ const pb::SymbolStatus& pbStatus = pbEntry.symbol_status();
+ if (pbStatus.has_source()) {
+ deserializeSourceFromPb(pbStatus.source(), *mSourcePool,
+ &entry->symbolStatus.source);
+ }
+
+ if (pbStatus.has_comment()) {
+ entry->symbolStatus.comment = pbStatus.comment();
+ }
+
+ SymbolState visibility =
+ deserializeVisibilityFromPb(pbStatus.visibility());
+ entry->symbolStatus.state = visibility;
+
+ if (visibility == SymbolState::kPublic) {
+ // This is a public symbol, we must encode the ID now if there is
+ // one.
+ if (pbEntry.has_id()) {
+ entry->id = static_cast<uint16_t>(pbEntry.id());
}
- ResourceTableType* type = pkg->findOrCreateType(*resType);
-
- for (const pb::Entry& pbEntry : pbType.entries()) {
- ResourceEntry* entry = type->findOrCreateEntry(pbEntry.name());
-
- // Deserialize the symbol status (public/private with source and comments).
- if (pbEntry.has_symbol_status()) {
- const pb::SymbolStatus& pbStatus = pbEntry.symbol_status();
- if (pbStatus.has_source()) {
- deserializeSourceFromPb(pbStatus.source(), *mSourcePool,
- &entry->symbolStatus.source);
- }
-
- if (pbStatus.has_comment()) {
- entry->symbolStatus.comment = pbStatus.comment();
- }
-
- SymbolState visibility = deserializeVisibilityFromPb(pbStatus.visibility());
- entry->symbolStatus.state = visibility;
-
- if (visibility == SymbolState::kPublic) {
- // This is a public symbol, we must encode the ID now if there is one.
- if (pbEntry.has_id()) {
- entry->id = static_cast<uint16_t>(pbEntry.id());
- }
-
- if (type->symbolStatus.state != SymbolState::kPublic) {
- // If the type has not been made public, do so now.
- type->symbolStatus.state = SymbolState::kPublic;
- if (pbType.has_id()) {
- type->id = static_cast<uint8_t>(pbType.id());
- }
- }
- } else if (visibility == SymbolState::kPrivate) {
- if (type->symbolStatus.state == SymbolState::kUndefined) {
- type->symbolStatus.state = SymbolState::kPrivate;
- }
- }
- }
-
- ResourceId resId(pbPackage.package_id(), pbType.id(), pbEntry.id());
- if (resId.isValid()) {
- idIndex[resId] = ResourceNameRef(pkg->name, type->type, entry->name);
- }
-
- for (const pb::ConfigValue& pbConfigValue : pbEntry.config_values()) {
- const pb::ConfigDescription& pbConfig = pbConfigValue.config();
-
- ConfigDescription config;
- if (!deserializeConfigDescriptionFromPb(pbConfig, &config)) {
- mDiag->error(DiagMessage(mSource) << "invalid configuration");
- return {};
- }
-
- ResourceConfigValue* configValue = entry->findOrCreateValue(config,
- pbConfig.product());
- if (configValue->value) {
- // Duplicate config.
- mDiag->error(DiagMessage(mSource) << "duplicate configuration");
- return {};
- }
-
- configValue->value = deserializeValueFromPb(pbConfigValue.value(),
- config, &table->stringPool);
- if (!configValue->value) {
- return {};
- }
- }
+ if (type->symbolStatus.state != SymbolState::kPublic) {
+ // If the type has not been made public, do so now.
+ type->symbolStatus.state = SymbolState::kPublic;
+ if (pbType.has_id()) {
+ type->id = static_cast<uint8_t>(pbType.id());
+ }
}
+ } else if (visibility == SymbolState::kPrivate) {
+ if (type->symbolStatus.state == SymbolState::kUndefined) {
+ type->symbolStatus.state = SymbolState::kPrivate;
+ }
+ }
}
- ReferenceIdToNameVisitor visitor(&idIndex);
- visitAllValuesInPackage(pkg, &visitor);
- return true;
+ ResourceId resId(pbPackage.package_id(), pbType.id(), pbEntry.id());
+ if (resId.isValid()) {
+ idIndex[resId] = ResourceNameRef(pkg->name, type->type, entry->name);
+ }
+
+ for (const pb::ConfigValue& pbConfigValue : pbEntry.config_values()) {
+ const pb::ConfigDescription& pbConfig = pbConfigValue.config();
+
+ ConfigDescription config;
+ if (!deserializeConfigDescriptionFromPb(pbConfig, &config)) {
+ mDiag->error(DiagMessage(mSource) << "invalid configuration");
+ return {};
+ }
+
+ ResourceConfigValue* configValue =
+ entry->findOrCreateValue(config, pbConfig.product());
+ if (configValue->value) {
+ // Duplicate config.
+ mDiag->error(DiagMessage(mSource) << "duplicate configuration");
+ return {};
+ }
+
+ configValue->value = deserializeValueFromPb(
+ pbConfigValue.value(), config, &table->stringPool);
+ if (!configValue->value) {
+ return {};
+ }
+ }
+ }
}
-private:
- std::unique_ptr<Item> deserializeItemFromPb(const pb::Item& pbItem,
+ ReferenceIdToNameVisitor visitor(&idIndex);
+ visitAllValuesInPackage(pkg, &visitor);
+ return true;
+ }
+
+ private:
+ std::unique_ptr<Item> deserializeItemFromPb(const pb::Item& pbItem,
+ const ConfigDescription& config,
+ StringPool* pool) {
+ if (pbItem.has_ref()) {
+ const pb::Reference& pbRef = pbItem.ref();
+ std::unique_ptr<Reference> ref = util::make_unique<Reference>();
+ if (!deserializeReferenceFromPb(pbRef, ref.get())) {
+ return {};
+ }
+ return std::move(ref);
+
+ } else if (pbItem.has_prim()) {
+ const pb::Primitive& pbPrim = pbItem.prim();
+ android::Res_value prim = {};
+ prim.dataType = static_cast<uint8_t>(pbPrim.type());
+ prim.data = pbPrim.data();
+ return util::make_unique<BinaryPrimitive>(prim);
+
+ } else if (pbItem.has_id()) {
+ return util::make_unique<Id>();
+
+ } else if (pbItem.has_str()) {
+ const uint32_t idx = pbItem.str().idx();
+ const std::string str = util::getString(*mValuePool, idx);
+
+ const android::ResStringPool_span* spans = mValuePool->styleAt(idx);
+ if (spans && spans->name.index != android::ResStringPool_span::END) {
+ StyleString styleStr = {str};
+ while (spans->name.index != android::ResStringPool_span::END) {
+ styleStr.spans.push_back(
+ Span{util::getString(*mValuePool, spans->name.index),
+ spans->firstChar, spans->lastChar});
+ spans++;
+ }
+ return util::make_unique<StyledString>(pool->makeRef(
+ styleStr,
+ StringPool::Context(StringPool::Context::kStylePriority, config)));
+ }
+ return util::make_unique<String>(
+ pool->makeRef(str, StringPool::Context(config)));
+
+ } else if (pbItem.has_raw_str()) {
+ const uint32_t idx = pbItem.raw_str().idx();
+ const std::string str = util::getString(*mValuePool, idx);
+ return util::make_unique<RawString>(
+ pool->makeRef(str, StringPool::Context(config)));
+
+ } else if (pbItem.has_file()) {
+ const uint32_t idx = pbItem.file().path_idx();
+ const std::string str = util::getString(*mValuePool, idx);
+ return util::make_unique<FileReference>(pool->makeRef(
+ str,
+ StringPool::Context(StringPool::Context::kHighPriority, config)));
+
+ } else {
+ mDiag->error(DiagMessage(mSource) << "unknown item");
+ }
+ return {};
+ }
+
+ std::unique_ptr<Value> deserializeValueFromPb(const pb::Value& pbValue,
const ConfigDescription& config,
StringPool* pool) {
- if (pbItem.has_ref()) {
- const pb::Reference& pbRef = pbItem.ref();
- std::unique_ptr<Reference> ref = util::make_unique<Reference>();
- if (!deserializeReferenceFromPb(pbRef, ref.get())) {
- return {};
- }
- return std::move(ref);
+ const bool isWeak = pbValue.has_weak() ? pbValue.weak() : false;
- } else if (pbItem.has_prim()) {
- const pb::Primitive& pbPrim = pbItem.prim();
- android::Res_value prim = {};
- prim.dataType = static_cast<uint8_t>(pbPrim.type());
- prim.data = pbPrim.data();
- return util::make_unique<BinaryPrimitive>(prim);
-
- } else if (pbItem.has_id()) {
- return util::make_unique<Id>();
-
- } else if (pbItem.has_str()) {
- const uint32_t idx = pbItem.str().idx();
- const std::string str = util::getString(*mValuePool, idx);
-
- const android::ResStringPool_span* spans = mValuePool->styleAt(idx);
- if (spans && spans->name.index != android::ResStringPool_span::END) {
- StyleString styleStr = { str };
- while (spans->name.index != android::ResStringPool_span::END) {
- styleStr.spans.push_back(Span{
- util::getString(*mValuePool, spans->name.index),
- spans->firstChar,
- spans->lastChar
- });
- spans++;
- }
- return util::make_unique<StyledString>(
- pool->makeRef(styleStr, StringPool::Context{ 1, config }));
- }
- return util::make_unique<String>(
- pool->makeRef(str, StringPool::Context{ 1, config }));
-
- } else if (pbItem.has_raw_str()) {
- const uint32_t idx = pbItem.raw_str().idx();
- const std::string str = util::getString(*mValuePool, idx);
- return util::make_unique<RawString>(
- pool->makeRef(str, StringPool::Context{ 1, config }));
-
- } else if (pbItem.has_file()) {
- const uint32_t idx = pbItem.file().path_idx();
- const std::string str = util::getString(*mValuePool, idx);
- return util::make_unique<FileReference>(
- pool->makeRef(str, StringPool::Context{ 0, config }));
-
- } else {
- mDiag->error(DiagMessage(mSource) << "unknown item");
- }
+ std::unique_ptr<Value> value;
+ if (pbValue.has_item()) {
+ value = deserializeItemFromPb(pbValue.item(), config, pool);
+ if (!value) {
return {};
- }
+ }
- std::unique_ptr<Value> deserializeValueFromPb(const pb::Value& pbValue,
- const ConfigDescription& config,
- StringPool* pool) {
- const bool isWeak = pbValue.has_weak() ? pbValue.weak() : false;
-
- std::unique_ptr<Value> value;
- if (pbValue.has_item()) {
- value = deserializeItemFromPb(pbValue.item(), config, pool);
- if (!value) {
- return {};
- }
-
- } else if (pbValue.has_compound_value()) {
- const pb::CompoundValue& pbCompoundValue = pbValue.compound_value();
- if (pbCompoundValue.has_attr()) {
- const pb::Attribute& pbAttr = pbCompoundValue.attr();
- std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
- attr->typeMask = pbAttr.format_flags();
- attr->minInt = pbAttr.min_int();
- attr->maxInt = pbAttr.max_int();
- for (const pb::Attribute_Symbol& pbSymbol : pbAttr.symbols()) {
- Attribute::Symbol symbol;
- deserializeItemCommon(pbSymbol, &symbol.symbol);
- if (!deserializeReferenceFromPb(pbSymbol.name(), &symbol.symbol)) {
- return {};
- }
- symbol.value = pbSymbol.value();
- attr->symbols.push_back(std::move(symbol));
- }
- value = std::move(attr);
-
- } else if (pbCompoundValue.has_style()) {
- const pb::Style& pbStyle = pbCompoundValue.style();
- std::unique_ptr<Style> style = util::make_unique<Style>();
- if (pbStyle.has_parent()) {
- style->parent = Reference();
- if (!deserializeReferenceFromPb(pbStyle.parent(), &style->parent.value())) {
- return {};
- }
-
- if (pbStyle.has_parent_source()) {
- Source parentSource;
- deserializeSourceFromPb(pbStyle.parent_source(), *mSourcePool,
- &parentSource);
- style->parent.value().setSource(std::move(parentSource));
- }
- }
-
- for (const pb::Style_Entry& pbEntry : pbStyle.entries()) {
- Style::Entry entry;
- deserializeItemCommon(pbEntry, &entry.key);
- if (!deserializeReferenceFromPb(pbEntry.key(), &entry.key)) {
- return {};
- }
-
- entry.value = deserializeItemFromPb(pbEntry.item(), config, pool);
- if (!entry.value) {
- return {};
- }
-
- deserializeItemCommon(pbEntry, entry.value.get());
- style->entries.push_back(std::move(entry));
- }
- value = std::move(style);
-
- } else if (pbCompoundValue.has_styleable()) {
- const pb::Styleable& pbStyleable = pbCompoundValue.styleable();
- std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
- for (const pb::Styleable_Entry& pbEntry : pbStyleable.entries()) {
- Reference attrRef;
- deserializeItemCommon(pbEntry, &attrRef);
- deserializeReferenceFromPb(pbEntry.attr(), &attrRef);
- styleable->entries.push_back(std::move(attrRef));
- }
- value = std::move(styleable);
-
- } else if (pbCompoundValue.has_array()) {
- const pb::Array& pbArray = pbCompoundValue.array();
- std::unique_ptr<Array> array = util::make_unique<Array>();
- for (const pb::Array_Entry& pbEntry : pbArray.entries()) {
- std::unique_ptr<Item> item = deserializeItemFromPb(pbEntry.item(), config,
- pool);
- if (!item) {
- return {};
- }
-
- deserializeItemCommon(pbEntry, item.get());
- array->items.push_back(std::move(item));
- }
- value = std::move(array);
-
- } else if (pbCompoundValue.has_plural()) {
- const pb::Plural& pbPlural = pbCompoundValue.plural();
- std::unique_ptr<Plural> plural = util::make_unique<Plural>();
- for (const pb::Plural_Entry& pbEntry : pbPlural.entries()) {
- size_t pluralIdx = deserializePluralEnumFromPb(pbEntry.arity());
- plural->values[pluralIdx] = deserializeItemFromPb(pbEntry.item(), config,
- pool);
- if (!plural->values[pluralIdx]) {
- return {};
- }
-
- deserializeItemCommon(pbEntry, plural->values[pluralIdx].get());
- }
- value = std::move(plural);
-
- } else {
- mDiag->error(DiagMessage(mSource) << "unknown compound value");
- return {};
- }
- } else {
- mDiag->error(DiagMessage(mSource) << "unknown value");
+ } else if (pbValue.has_compound_value()) {
+ const pb::CompoundValue& pbCompoundValue = pbValue.compound_value();
+ if (pbCompoundValue.has_attr()) {
+ const pb::Attribute& pbAttr = pbCompoundValue.attr();
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
+ attr->typeMask = pbAttr.format_flags();
+ attr->minInt = pbAttr.min_int();
+ attr->maxInt = pbAttr.max_int();
+ for (const pb::Attribute_Symbol& pbSymbol : pbAttr.symbols()) {
+ Attribute::Symbol symbol;
+ deserializeItemCommon(pbSymbol, &symbol.symbol);
+ if (!deserializeReferenceFromPb(pbSymbol.name(), &symbol.symbol)) {
return {};
+ }
+ symbol.value = pbSymbol.value();
+ attr->symbols.push_back(std::move(symbol));
+ }
+ value = std::move(attr);
+
+ } else if (pbCompoundValue.has_style()) {
+ const pb::Style& pbStyle = pbCompoundValue.style();
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+ if (pbStyle.has_parent()) {
+ style->parent = Reference();
+ if (!deserializeReferenceFromPb(pbStyle.parent(),
+ &style->parent.value())) {
+ return {};
+ }
+
+ if (pbStyle.has_parent_source()) {
+ Source parentSource;
+ deserializeSourceFromPb(pbStyle.parent_source(), *mSourcePool,
+ &parentSource);
+ style->parent.value().setSource(std::move(parentSource));
+ }
}
- assert(value && "forgot to set value");
+ for (const pb::Style_Entry& pbEntry : pbStyle.entries()) {
+ Style::Entry entry;
+ deserializeItemCommon(pbEntry, &entry.key);
+ if (!deserializeReferenceFromPb(pbEntry.key(), &entry.key)) {
+ return {};
+ }
- value->setWeak(isWeak);
- deserializeItemCommon(pbValue, value.get());
- return value;
+ entry.value = deserializeItemFromPb(pbEntry.item(), config, pool);
+ if (!entry.value) {
+ return {};
+ }
+
+ deserializeItemCommon(pbEntry, entry.value.get());
+ style->entries.push_back(std::move(entry));
+ }
+ value = std::move(style);
+
+ } else if (pbCompoundValue.has_styleable()) {
+ const pb::Styleable& pbStyleable = pbCompoundValue.styleable();
+ std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+ for (const pb::Styleable_Entry& pbEntry : pbStyleable.entries()) {
+ Reference attrRef;
+ deserializeItemCommon(pbEntry, &attrRef);
+ deserializeReferenceFromPb(pbEntry.attr(), &attrRef);
+ styleable->entries.push_back(std::move(attrRef));
+ }
+ value = std::move(styleable);
+
+ } else if (pbCompoundValue.has_array()) {
+ const pb::Array& pbArray = pbCompoundValue.array();
+ std::unique_ptr<Array> array = util::make_unique<Array>();
+ for (const pb::Array_Entry& pbEntry : pbArray.entries()) {
+ std::unique_ptr<Item> item =
+ deserializeItemFromPb(pbEntry.item(), config, pool);
+ if (!item) {
+ return {};
+ }
+
+ deserializeItemCommon(pbEntry, item.get());
+ array->items.push_back(std::move(item));
+ }
+ value = std::move(array);
+
+ } else if (pbCompoundValue.has_plural()) {
+ const pb::Plural& pbPlural = pbCompoundValue.plural();
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ for (const pb::Plural_Entry& pbEntry : pbPlural.entries()) {
+ size_t pluralIdx = deserializePluralEnumFromPb(pbEntry.arity());
+ plural->values[pluralIdx] =
+ deserializeItemFromPb(pbEntry.item(), config, pool);
+ if (!plural->values[pluralIdx]) {
+ return {};
+ }
+
+ deserializeItemCommon(pbEntry, plural->values[pluralIdx].get());
+ }
+ value = std::move(plural);
+
+ } else {
+ mDiag->error(DiagMessage(mSource) << "unknown compound value");
+ return {};
+ }
+ } else {
+ mDiag->error(DiagMessage(mSource) << "unknown value");
+ return {};
}
- bool deserializeReferenceFromPb(const pb::Reference& pbRef, Reference* outRef) {
- outRef->referenceType = deserializeReferenceTypeFromPb(pbRef.type());
- outRef->privateReference = pbRef.private_();
+ assert(value && "forgot to set value");
- if (!pbRef.has_id() && !pbRef.has_symbol_idx()) {
- return false;
- }
+ value->setWeak(isWeak);
+ deserializeItemCommon(pbValue, value.get());
+ return value;
+ }
- if (pbRef.has_id()) {
- outRef->id = ResourceId(pbRef.id());
- }
+ bool deserializeReferenceFromPb(const pb::Reference& pbRef,
+ Reference* outRef) {
+ outRef->referenceType = deserializeReferenceTypeFromPb(pbRef.type());
+ outRef->privateReference = pbRef.private_();
- if (pbRef.has_symbol_idx()) {
- const std::string strSymbol = util::getString(*mSymbolPool, pbRef.symbol_idx());
- ResourceNameRef nameRef;
- if (!ResourceUtils::parseResourceName(strSymbol, &nameRef, nullptr)) {
- mDiag->error(DiagMessage(mSource) << "invalid reference name '"
- << strSymbol << "'");
- return false;
- }
-
- outRef->name = nameRef.toResourceName();
- }
- return true;
+ if (!pbRef.has_id() && !pbRef.has_symbol_idx()) {
+ return false;
}
- template <typename T>
- void deserializeItemCommon(const T& pbItem, Value* outValue) {
- if (pbItem.has_source()) {
- Source source;
- deserializeSourceFromPb(pbItem.source(), *mSourcePool, &source);
- outValue->setSource(std::move(source));
- }
-
- if (pbItem.has_comment()) {
- outValue->setComment(pbItem.comment());
- }
+ if (pbRef.has_id()) {
+ outRef->id = ResourceId(pbRef.id());
}
-private:
- const android::ResStringPool* mValuePool;
- const android::ResStringPool* mSourcePool;
- const android::ResStringPool* mSymbolPool;
- const Source mSource;
- IDiagnostics* mDiag;
+ if (pbRef.has_symbol_idx()) {
+ const std::string strSymbol =
+ util::getString(*mSymbolPool, pbRef.symbol_idx());
+ ResourceNameRef nameRef;
+ if (!ResourceUtils::parseResourceName(strSymbol, &nameRef, nullptr)) {
+ mDiag->error(DiagMessage(mSource) << "invalid reference name '"
+ << strSymbol << "'");
+ return false;
+ }
+
+ outRef->name = nameRef.toResourceName();
+ }
+ return true;
+ }
+
+ template <typename T>
+ void deserializeItemCommon(const T& pbItem, Value* outValue) {
+ if (pbItem.has_source()) {
+ Source source;
+ deserializeSourceFromPb(pbItem.source(), *mSourcePool, &source);
+ outValue->setSource(std::move(source));
+ }
+
+ if (pbItem.has_comment()) {
+ outValue->setComment(pbItem.comment());
+ }
+ }
+
+ private:
+ const android::ResStringPool* mValuePool;
+ const android::ResStringPool* mSourcePool;
+ const android::ResStringPool* mSymbolPool;
+ const Source mSource;
+ IDiagnostics* mDiag;
};
-} // namespace
+} // namespace
-std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable,
- const Source& source,
- IDiagnostics* diag) {
- // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which
- // causes errors when qualifying it with android::
- using namespace android;
+std::unique_ptr<ResourceTable> deserializeTableFromPb(
+ const pb::ResourceTable& pbTable, const Source& source,
+ IDiagnostics* diag) {
+ // We import the android namespace because on Windows NO_ERROR is a macro, not
+ // an enum, which
+ // causes errors when qualifying it with android::
+ using namespace android;
- std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+ std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
- if (!pbTable.has_string_pool()) {
- diag->error(DiagMessage(source) << "no string pool found");
- return {};
- }
+ if (!pbTable.has_string_pool()) {
+ diag->error(DiagMessage(source) << "no string pool found");
+ return {};
+ }
- ResStringPool valuePool;
- status_t result = valuePool.setTo(pbTable.string_pool().data().data(),
- pbTable.string_pool().data().size());
+ ResStringPool valuePool;
+ status_t result = valuePool.setTo(pbTable.string_pool().data().data(),
+ pbTable.string_pool().data().size());
+ if (result != NO_ERROR) {
+ diag->error(DiagMessage(source) << "invalid string pool");
+ return {};
+ }
+
+ ResStringPool sourcePool;
+ if (pbTable.has_source_pool()) {
+ result = sourcePool.setTo(pbTable.source_pool().data().data(),
+ pbTable.source_pool().data().size());
if (result != NO_ERROR) {
- diag->error(DiagMessage(source) << "invalid string pool");
- return {};
+ diag->error(DiagMessage(source) << "invalid source pool");
+ return {};
}
+ }
- ResStringPool sourcePool;
- if (pbTable.has_source_pool()) {
- result = sourcePool.setTo(pbTable.source_pool().data().data(),
- pbTable.source_pool().data().size());
- if (result != NO_ERROR) {
- diag->error(DiagMessage(source) << "invalid source pool");
- return {};
- }
+ ResStringPool symbolPool;
+ if (pbTable.has_symbol_pool()) {
+ result = symbolPool.setTo(pbTable.symbol_pool().data().data(),
+ pbTable.symbol_pool().data().size());
+ if (result != NO_ERROR) {
+ diag->error(DiagMessage(source) << "invalid symbol pool");
+ return {};
}
+ }
- ResStringPool symbolPool;
- if (pbTable.has_symbol_pool()) {
- result = symbolPool.setTo(pbTable.symbol_pool().data().data(),
- pbTable.symbol_pool().data().size());
- if (result != NO_ERROR) {
- diag->error(DiagMessage(source) << "invalid symbol pool");
- return {};
- }
+ PackagePbDeserializer packagePbDeserializer(&valuePool, &sourcePool,
+ &symbolPool, source, diag);
+ for (const pb::Package& pbPackage : pbTable.packages()) {
+ if (!packagePbDeserializer.deserializeFromPb(pbPackage, table.get())) {
+ return {};
}
-
- PackagePbDeserializer packagePbDeserializer(&valuePool, &sourcePool, &symbolPool, source, diag);
- for (const pb::Package& pbPackage : pbTable.packages()) {
- if (!packagePbDeserializer.deserializeFromPb(pbPackage, table.get())) {
- return {};
- }
- }
- return table;
+ }
+ return table;
}
-std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(const pb::CompiledFile& pbFile,
- const Source& source,
- IDiagnostics* diag) {
- std::unique_ptr<ResourceFile> file = util::make_unique<ResourceFile>();
+std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(
+ const pb::CompiledFile& pbFile, const Source& source, IDiagnostics* diag) {
+ std::unique_ptr<ResourceFile> file = util::make_unique<ResourceFile>();
- ResourceNameRef nameRef;
+ ResourceNameRef nameRef;
- // Need to create an lvalue here so that nameRef can point to something real.
- if (!ResourceUtils::parseResourceName(pbFile.resource_name(), &nameRef)) {
- diag->error(DiagMessage(source) << "invalid resource name in compiled file header: "
- << pbFile.resource_name());
- return {};
+ // Need to create an lvalue here so that nameRef can point to something real.
+ if (!ResourceUtils::parseResourceName(pbFile.resource_name(), &nameRef)) {
+ diag->error(DiagMessage(source)
+ << "invalid resource name in compiled file header: "
+ << pbFile.resource_name());
+ return {};
+ }
+ file->name = nameRef.toResourceName();
+ file->source.path = pbFile.source_path();
+ deserializeConfigDescriptionFromPb(pbFile.config(), &file->config);
+
+ for (const pb::CompiledFile_Symbol& pbSymbol : pbFile.exported_symbols()) {
+ // Need to create an lvalue here so that nameRef can point to something
+ // real.
+ if (!ResourceUtils::parseResourceName(pbSymbol.resource_name(), &nameRef)) {
+ diag->error(DiagMessage(source)
+ << "invalid resource name for exported symbol in "
+ "compiled file header: "
+ << pbFile.resource_name());
+ return {};
}
- file->name = nameRef.toResourceName();
- file->source.path = pbFile.source_path();
- deserializeConfigDescriptionFromPb(pbFile.config(), &file->config);
-
- for (const pb::CompiledFile_Symbol& pbSymbol : pbFile.exported_symbols()) {
- // Need to create an lvalue here so that nameRef can point to something real.
- if (!ResourceUtils::parseResourceName(pbSymbol.resource_name(), &nameRef)) {
- diag->error(DiagMessage(source) << "invalid resource name for exported symbol in "
- "compiled file header: "
- << pbFile.resource_name());
- return {};
- }
- file->exportedSymbols.push_back(
- SourcedResourceName{ nameRef.toResourceName(), pbSymbol.line_no() });
- }
- return file;
+ file->exportedSymbols.push_back(
+ SourcedResourceName{nameRef.toResourceName(), pbSymbol.line_no()});
+ }
+ return file;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/split/TableSplitter.h b/tools/aapt2/split/TableSplitter.h
index 2fa5c47..d7ecc82 100644
--- a/tools/aapt2/split/TableSplitter.h
+++ b/tools/aapt2/split/TableSplitter.h
@@ -29,50 +29,49 @@
namespace aapt {
struct SplitConstraints {
- std::set<ConfigDescription> configs;
+ std::set<ConfigDescription> configs;
};
struct TableSplitterOptions {
- /**
- * The preferred density to keep in the table, stripping out all others.
- */
- Maybe<uint16_t> preferredDensity;
+ /**
+ * The preferred density to keep in the table, stripping out all others.
+ */
+ Maybe<uint16_t> preferredDensity;
- /**
- * Configuration filter that determines which resource configuration values end up in
- * the final table.
- */
- IConfigFilter* configFilter = nullptr;
+ /**
+ * Configuration filter that determines which resource configuration values
+ * end up in
+ * the final table.
+ */
+ IConfigFilter* configFilter = nullptr;
};
class TableSplitter {
-public:
- TableSplitter(const std::vector<SplitConstraints>& splits,
- const TableSplitterOptions& options) :
- mSplitConstraints(splits), mPreferredDensity(options.preferredDensity),
- mConfigFilter(options.configFilter) {
- for (size_t i = 0; i < mSplitConstraints.size(); i++) {
- mSplits.push_back(util::make_unique<ResourceTable>());
- }
+ public:
+ TableSplitter(const std::vector<SplitConstraints>& splits,
+ const TableSplitterOptions& options)
+ : mSplitConstraints(splits),
+ mPreferredDensity(options.preferredDensity),
+ mConfigFilter(options.configFilter) {
+ for (size_t i = 0; i < mSplitConstraints.size(); i++) {
+ mSplits.push_back(util::make_unique<ResourceTable>());
}
+ }
- bool verifySplitConstraints(IAaptContext* context);
+ bool verifySplitConstraints(IAaptContext* context);
- void splitTable(ResourceTable* originalTable);
+ void splitTable(ResourceTable* originalTable);
- std::vector<std::unique_ptr<ResourceTable>>& getSplits() {
- return mSplits;
- }
+ std::vector<std::unique_ptr<ResourceTable>>& getSplits() { return mSplits; }
-private:
- std::vector<SplitConstraints> mSplitConstraints;
- std::vector<std::unique_ptr<ResourceTable>> mSplits;
- Maybe<uint16_t> mPreferredDensity;
- IConfigFilter* mConfigFilter;
+ private:
+ std::vector<SplitConstraints> mSplitConstraints;
+ std::vector<std::unique_ptr<ResourceTable>> mSplits;
+ Maybe<uint16_t> mPreferredDensity;
+ IConfigFilter* mConfigFilter;
- DISALLOW_COPY_AND_ASSIGN(TableSplitter);
+ DISALLOW_COPY_AND_ASSIGN(TableSplitter);
};
-
}
#endif /* AAPT_SPLIT_TABLESPLITTER_H */
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 637e991..c647159 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -29,232 +29,242 @@
namespace test {
class ResourceTableBuilder {
-private:
- DummyDiagnosticsImpl mDiagnostics;
- std::unique_ptr<ResourceTable> mTable = util::make_unique<ResourceTable>();
+ private:
+ DummyDiagnosticsImpl mDiagnostics;
+ std::unique_ptr<ResourceTable> mTable = util::make_unique<ResourceTable>();
-public:
- ResourceTableBuilder() = default;
+ public:
+ ResourceTableBuilder() = default;
- StringPool* getStringPool() {
- return &mTable->stringPool;
- }
+ StringPool* getStringPool() { return &mTable->stringPool; }
- ResourceTableBuilder& setPackageId(const StringPiece& packageName, uint8_t id) {
- ResourceTablePackage* package = mTable->createPackage(packageName, id);
- assert(package);
- return *this;
- }
+ ResourceTableBuilder& setPackageId(const StringPiece& packageName,
+ uint8_t id) {
+ ResourceTablePackage* package = mTable->createPackage(packageName, id);
+ assert(package);
+ return *this;
+ }
- ResourceTableBuilder& addSimple(const StringPiece& name, const ResourceId& id = {}) {
- return addValue(name, id, util::make_unique<Id>());
- }
+ ResourceTableBuilder& addSimple(const StringPiece& name,
+ const ResourceId& id = {}) {
+ return addValue(name, id, util::make_unique<Id>());
+ }
- ResourceTableBuilder& addSimple(const StringPiece& name, const ConfigDescription& config,
- const ResourceId& id = {}) {
- return addValue(name, config, id, util::make_unique<Id>());
- }
+ ResourceTableBuilder& addSimple(const StringPiece& name,
+ const ConfigDescription& config,
+ const ResourceId& id = {}) {
+ return addValue(name, config, id, util::make_unique<Id>());
+ }
- ResourceTableBuilder& addReference(const StringPiece& name, const StringPiece& ref) {
- return addReference(name, {}, ref);
- }
+ ResourceTableBuilder& addReference(const StringPiece& name,
+ const StringPiece& ref) {
+ return addReference(name, {}, ref);
+ }
- ResourceTableBuilder& addReference(const StringPiece& name, const ResourceId& id,
- const StringPiece& ref) {
- return addValue(name, id, util::make_unique<Reference>(parseNameOrDie(ref)));
- }
+ ResourceTableBuilder& addReference(const StringPiece& name,
+ const ResourceId& id,
+ const StringPiece& ref) {
+ return addValue(name, id,
+ util::make_unique<Reference>(parseNameOrDie(ref)));
+ }
- ResourceTableBuilder& addString(const StringPiece& name, const StringPiece& str) {
- return addString(name, {}, str);
- }
+ ResourceTableBuilder& addString(const StringPiece& name,
+ const StringPiece& str) {
+ return addString(name, {}, str);
+ }
- ResourceTableBuilder& addString(const StringPiece& name, const ResourceId& id,
- const StringPiece& str) {
- return addValue(name, id, util::make_unique<String>(mTable->stringPool.makeRef(str)));
- }
+ ResourceTableBuilder& addString(const StringPiece& name, const ResourceId& id,
+ const StringPiece& str) {
+ return addValue(name, id,
+ util::make_unique<String>(mTable->stringPool.makeRef(str)));
+ }
- ResourceTableBuilder& addString(const StringPiece& name, const ResourceId& id,
- const ConfigDescription& config, const StringPiece& str) {
- return addValue(name, config, id,
- util::make_unique<String>(mTable->stringPool.makeRef(str)));
- }
+ ResourceTableBuilder& addString(const StringPiece& name, const ResourceId& id,
+ const ConfigDescription& config,
+ const StringPiece& str) {
+ return addValue(name, config, id,
+ util::make_unique<String>(mTable->stringPool.makeRef(str)));
+ }
- ResourceTableBuilder& addFileReference(const StringPiece& name, const StringPiece& path) {
- return addFileReference(name, {}, path);
- }
+ ResourceTableBuilder& addFileReference(const StringPiece& name,
+ const StringPiece& path) {
+ return addFileReference(name, {}, path);
+ }
- ResourceTableBuilder& addFileReference(const StringPiece& name, const ResourceId& id,
- const StringPiece& path) {
- return addValue(name, id,
- util::make_unique<FileReference>(mTable->stringPool.makeRef(path)));
- }
+ ResourceTableBuilder& addFileReference(const StringPiece& name,
+ const ResourceId& id,
+ const StringPiece& path) {
+ return addValue(name, id, util::make_unique<FileReference>(
+ mTable->stringPool.makeRef(path)));
+ }
- ResourceTableBuilder& addFileReference(const StringPiece& name, const StringPiece& path,
- const ConfigDescription& config) {
- return addValue(name, config, {},
- util::make_unique<FileReference>(mTable->stringPool.makeRef(path)));
- }
+ ResourceTableBuilder& addFileReference(const StringPiece& name,
+ const StringPiece& path,
+ const ConfigDescription& config) {
+ return addValue(name, config, {}, util::make_unique<FileReference>(
+ mTable->stringPool.makeRef(path)));
+ }
- ResourceTableBuilder& addValue(const StringPiece& name,
- std::unique_ptr<Value> value) {
- return addValue(name, {}, std::move(value));
- }
+ ResourceTableBuilder& addValue(const StringPiece& name,
+ std::unique_ptr<Value> value) {
+ return addValue(name, {}, std::move(value));
+ }
- ResourceTableBuilder& addValue(const StringPiece& name, const ResourceId& id,
- std::unique_ptr<Value> value) {
- return addValue(name, {}, id, std::move(value));
- }
+ ResourceTableBuilder& addValue(const StringPiece& name, const ResourceId& id,
+ std::unique_ptr<Value> value) {
+ return addValue(name, {}, id, std::move(value));
+ }
- ResourceTableBuilder& addValue(const StringPiece& name, const ConfigDescription& config,
- const ResourceId& id, std::unique_ptr<Value> value) {
- ResourceName resName = parseNameOrDie(name);
- bool result = mTable->addResourceAllowMangled(resName, id, config, {},
- std::move(value), &mDiagnostics);
- assert(result);
- return *this;
- }
+ ResourceTableBuilder& addValue(const StringPiece& name,
+ const ConfigDescription& config,
+ const ResourceId& id,
+ std::unique_ptr<Value> value) {
+ ResourceName resName = parseNameOrDie(name);
+ bool result = mTable->addResourceAllowMangled(
+ resName, id, config, {}, std::move(value), &mDiagnostics);
+ assert(result);
+ return *this;
+ }
- ResourceTableBuilder& setSymbolState(const StringPiece& name, const ResourceId& id,
- SymbolState state) {
- ResourceName resName = parseNameOrDie(name);
- Symbol symbol;
- symbol.state = state;
- bool result = mTable->setSymbolStateAllowMangled(resName, id, symbol, &mDiagnostics);
- assert(result);
- return *this;
- }
+ ResourceTableBuilder& setSymbolState(const StringPiece& name,
+ const ResourceId& id,
+ SymbolState state) {
+ ResourceName resName = parseNameOrDie(name);
+ Symbol symbol;
+ symbol.state = state;
+ bool result =
+ mTable->setSymbolStateAllowMangled(resName, id, symbol, &mDiagnostics);
+ assert(result);
+ return *this;
+ }
- std::unique_ptr<ResourceTable> build() {
- return std::move(mTable);
- }
+ std::unique_ptr<ResourceTable> build() { return std::move(mTable); }
};
-inline std::unique_ptr<Reference> buildReference(const StringPiece& ref,
- const Maybe<ResourceId>& id = {}) {
- std::unique_ptr<Reference> reference = util::make_unique<Reference>(parseNameOrDie(ref));
- reference->id = id;
- return reference;
+inline std::unique_ptr<Reference> buildReference(
+ const StringPiece& ref, const Maybe<ResourceId>& id = {}) {
+ std::unique_ptr<Reference> reference =
+ util::make_unique<Reference>(parseNameOrDie(ref));
+ reference->id = id;
+ return reference;
}
-inline std::unique_ptr<BinaryPrimitive> buildPrimitive(uint8_t type, uint32_t data) {
- android::Res_value value = {};
- value.size = sizeof(value);
- value.dataType = type;
- value.data = data;
- return util::make_unique<BinaryPrimitive>(value);
+inline std::unique_ptr<BinaryPrimitive> buildPrimitive(uint8_t type,
+ uint32_t data) {
+ android::Res_value value = {};
+ value.size = sizeof(value);
+ value.dataType = type;
+ value.data = data;
+ return util::make_unique<BinaryPrimitive>(value);
}
template <typename T>
class ValueBuilder {
-private:
- std::unique_ptr<Value> mValue;
+ private:
+ std::unique_ptr<Value> mValue;
-public:
- template <typename... Args>
- explicit ValueBuilder(Args&&... args) : mValue(new T{ std::forward<Args>(args)... }) {
- }
+ public:
+ template <typename... Args>
+ explicit ValueBuilder(Args&&... args)
+ : mValue(new T{std::forward<Args>(args)...}) {}
- template <typename... Args>
- ValueBuilder& setSource(Args&&... args) {
- mValue->setSource(Source{ std::forward<Args>(args)... });
- return *this;
- }
+ template <typename... Args>
+ ValueBuilder& setSource(Args&&... args) {
+ mValue->setSource(Source{std::forward<Args>(args)...});
+ return *this;
+ }
- ValueBuilder& setComment(const StringPiece& str) {
- mValue->setComment(str);
- return *this;
- }
+ ValueBuilder& setComment(const StringPiece& str) {
+ mValue->setComment(str);
+ return *this;
+ }
- std::unique_ptr<Value> build() {
- return std::move(mValue);
- }
+ std::unique_ptr<Value> build() { return std::move(mValue); }
};
class AttributeBuilder {
-private:
- std::unique_ptr<Attribute> mAttr;
+ private:
+ std::unique_ptr<Attribute> mAttr;
-public:
- explicit AttributeBuilder(bool weak = false) : mAttr(util::make_unique<Attribute>(weak)) {
- mAttr->typeMask = android::ResTable_map::TYPE_ANY;
- }
+ public:
+ explicit AttributeBuilder(bool weak = false)
+ : mAttr(util::make_unique<Attribute>(weak)) {
+ mAttr->typeMask = android::ResTable_map::TYPE_ANY;
+ }
- AttributeBuilder& setTypeMask(uint32_t typeMask) {
- mAttr->typeMask = typeMask;
- return *this;
- }
+ AttributeBuilder& setTypeMask(uint32_t typeMask) {
+ mAttr->typeMask = typeMask;
+ return *this;
+ }
- AttributeBuilder& addItem(const StringPiece& name, uint32_t value) {
- mAttr->symbols.push_back(Attribute::Symbol{
- Reference(ResourceName({}, ResourceType::kId, name)),
- value});
- return *this;
- }
+ AttributeBuilder& addItem(const StringPiece& name, uint32_t value) {
+ mAttr->symbols.push_back(Attribute::Symbol{
+ Reference(ResourceName({}, ResourceType::kId, name)), value});
+ return *this;
+ }
- std::unique_ptr<Attribute> build() {
- return std::move(mAttr);
- }
+ std::unique_ptr<Attribute> build() { return std::move(mAttr); }
};
class StyleBuilder {
-private:
- std::unique_ptr<Style> mStyle = util::make_unique<Style>();
+ private:
+ std::unique_ptr<Style> mStyle = util::make_unique<Style>();
-public:
- StyleBuilder& setParent(const StringPiece& str) {
- mStyle->parent = Reference(parseNameOrDie(str));
- return *this;
- }
+ public:
+ StyleBuilder& setParent(const StringPiece& str) {
+ mStyle->parent = Reference(parseNameOrDie(str));
+ return *this;
+ }
- StyleBuilder& addItem(const StringPiece& str, std::unique_ptr<Item> value) {
- mStyle->entries.push_back(Style::Entry{ Reference(parseNameOrDie(str)), std::move(value) });
- return *this;
- }
+ StyleBuilder& addItem(const StringPiece& str, std::unique_ptr<Item> value) {
+ mStyle->entries.push_back(
+ Style::Entry{Reference(parseNameOrDie(str)), std::move(value)});
+ return *this;
+ }
- StyleBuilder& addItem(const StringPiece& str, const ResourceId& id, std::unique_ptr<Item> value) {
- addItem(str, std::move(value));
- mStyle->entries.back().key.id = id;
- return *this;
- }
+ StyleBuilder& addItem(const StringPiece& str, const ResourceId& id,
+ std::unique_ptr<Item> value) {
+ addItem(str, std::move(value));
+ mStyle->entries.back().key.id = id;
+ return *this;
+ }
- std::unique_ptr<Style> build() {
- return std::move(mStyle);
- }
+ std::unique_ptr<Style> build() { return std::move(mStyle); }
};
class StyleableBuilder {
-private:
- std::unique_ptr<Styleable> mStyleable = util::make_unique<Styleable>();
+ private:
+ std::unique_ptr<Styleable> mStyleable = util::make_unique<Styleable>();
-public:
- StyleableBuilder& addItem(const StringPiece& str, const Maybe<ResourceId>& id = {}) {
- mStyleable->entries.push_back(Reference(parseNameOrDie(str)));
- mStyleable->entries.back().id = id;
- return *this;
- }
+ public:
+ StyleableBuilder& addItem(const StringPiece& str,
+ const Maybe<ResourceId>& id = {}) {
+ mStyleable->entries.push_back(Reference(parseNameOrDie(str)));
+ mStyleable->entries.back().id = id;
+ return *this;
+ }
- std::unique_ptr<Styleable> build() {
- return std::move(mStyleable);
- }
+ std::unique_ptr<Styleable> build() { return std::move(mStyleable); }
};
inline std::unique_ptr<xml::XmlResource> buildXmlDom(const StringPiece& str) {
- std::stringstream in;
- in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
- StdErrDiagnostics diag;
- std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, Source("test.xml"));
- assert(doc);
- return doc;
+ std::stringstream in;
+ in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
+ StdErrDiagnostics diag;
+ std::unique_ptr<xml::XmlResource> doc =
+ xml::inflate(&in, &diag, Source("test.xml"));
+ assert(doc);
+ return doc;
}
-inline std::unique_ptr<xml::XmlResource> buildXmlDomForPackageName(IAaptContext* context,
- const StringPiece& str) {
- std::unique_ptr<xml::XmlResource> doc = buildXmlDom(str);
- doc->file.name.package = context->getCompilationPackage();
- return doc;
+inline std::unique_ptr<xml::XmlResource> buildXmlDomForPackageName(
+ IAaptContext* context, const StringPiece& str) {
+ std::unique_ptr<xml::XmlResource> doc = buildXmlDom(str);
+ doc->file.name.package = context->getCompilationPackage();
+ return doc;
}
-} // namespace test
-} // namespace aapt
+} // namespace test
+} // namespace aapt
#endif /* AAPT_TEST_BUILDERS_H */
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 7fafcbe..2d571e7 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -30,7 +30,8 @@
#include <iostream>
//
-// GTEST 1.7 doesn't explicitly cast to bool, which causes explicit operators to fail to compile.
+// GTEST 1.7 doesn't explicitly cast to bool, which causes explicit operators to
+// fail to compile.
//
#define AAPT_ASSERT_TRUE(v) ASSERT_TRUE(bool(v))
#define AAPT_ASSERT_FALSE(v) ASSERT_FALSE(bool(v))
@@ -41,81 +42,83 @@
namespace test {
struct DummyDiagnosticsImpl : public IDiagnostics {
- void log(Level level, DiagMessageActual& actualMsg) override {
- switch (level) {
- case Level::Note:
- return;
+ void log(Level level, DiagMessageActual& actualMsg) override {
+ switch (level) {
+ case Level::Note:
+ return;
- case Level::Warn:
- std::cerr << actualMsg.source << ": warn: " << actualMsg.message << "." << std::endl;
- break;
+ case Level::Warn:
+ std::cerr << actualMsg.source << ": warn: " << actualMsg.message << "."
+ << std::endl;
+ break;
- case Level::Error:
- std::cerr << actualMsg.source << ": error: " << actualMsg.message << "." << std::endl;
- break;
- }
+ case Level::Error:
+ std::cerr << actualMsg.source << ": error: " << actualMsg.message << "."
+ << std::endl;
+ break;
}
+ }
};
inline IDiagnostics* getDiagnostics() {
- static DummyDiagnosticsImpl diag;
- return &diag;
+ static DummyDiagnosticsImpl diag;
+ return &diag;
}
inline ResourceName parseNameOrDie(const StringPiece& str) {
- ResourceNameRef ref;
- bool result = ResourceUtils::parseResourceName(str, &ref);
- assert(result && "invalid resource name");
- return ref.toResourceName();
+ ResourceNameRef ref;
+ bool result = ResourceUtils::parseResourceName(str, &ref);
+ assert(result && "invalid resource name");
+ return ref.toResourceName();
}
inline ConfigDescription parseConfigOrDie(const StringPiece& str) {
- ConfigDescription config;
- bool result = ConfigDescription::parse(str, &config);
- assert(result && "invalid configuration");
- return config;
+ ConfigDescription config;
+ bool result = ConfigDescription::parse(str, &config);
+ assert(result && "invalid configuration");
+ return config;
}
-template <typename T> T* getValueForConfigAndProduct(ResourceTable* table,
- const StringPiece& resName,
- const ConfigDescription& config,
- const StringPiece& product) {
- Maybe<ResourceTable::SearchResult> result = table->findResource(parseNameOrDie(resName));
- if (result) {
- ResourceConfigValue* configValue = result.value().entry->findValue(config, product);
- if (configValue) {
- return valueCast<T>(configValue->value.get());
- }
+template <typename T>
+T* getValueForConfigAndProduct(ResourceTable* table, const StringPiece& resName,
+ const ConfigDescription& config,
+ const StringPiece& product) {
+ Maybe<ResourceTable::SearchResult> result =
+ table->findResource(parseNameOrDie(resName));
+ if (result) {
+ ResourceConfigValue* configValue =
+ result.value().entry->findValue(config, product);
+ if (configValue) {
+ return valueCast<T>(configValue->value.get());
}
- return nullptr;
+ }
+ return nullptr;
}
-template <typename T> T* getValueForConfig(ResourceTable* table, const StringPiece& resName,
- const ConfigDescription& config) {
- return getValueForConfigAndProduct<T>(table, resName, config, {});
+template <typename T>
+T* getValueForConfig(ResourceTable* table, const StringPiece& resName,
+ const ConfigDescription& config) {
+ return getValueForConfigAndProduct<T>(table, resName, config, {});
}
-template <typename T> T* getValue(ResourceTable* table, const StringPiece& resName) {
- return getValueForConfig<T>(table, resName, {});
+template <typename T>
+T* getValue(ResourceTable* table, const StringPiece& resName) {
+ return getValueForConfig<T>(table, resName, {});
}
class TestFile : public io::IFile {
-private:
- Source mSource;
+ private:
+ Source mSource;
-public:
- explicit TestFile(const StringPiece& path) : mSource(path) {}
+ public:
+ explicit TestFile(const StringPiece& path) : mSource(path) {}
- std::unique_ptr<io::IData> openAsData() override {
- return {};
- }
+ std::unique_ptr<io::IData> openAsData() override { return {}; }
- const Source& getSource() const override {
- return mSource;
- }
+ const Source& getSource() const override { return mSource; }
};
-} // namespace test
-} // namespace aapt
+} // namespace test
+} // namespace aapt
#endif /* AAPT_TEST_COMMON_H */
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 54f16db..6c7f6f7 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -18,10 +18,10 @@
#define AAPT_TEST_CONTEXT_H
#include "NameMangler.h"
-#include "util/Util.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
#include "test/Common.h"
+#include "util/Util.h"
#include <cassert>
#include <list>
@@ -30,152 +30,143 @@
namespace test {
class Context : public IAaptContext {
-public:
- SymbolTable* getExternalSymbols() override {
- return &mSymbols;
- }
+ public:
+ SymbolTable* getExternalSymbols() override { return &mSymbols; }
- IDiagnostics* getDiagnostics() override {
- return &mDiagnostics;
- }
+ IDiagnostics* getDiagnostics() override { return &mDiagnostics; }
- const std::string& getCompilationPackage() override {
- assert(mCompilationPackage && "package name not set");
- return mCompilationPackage.value();
- }
+ const std::string& getCompilationPackage() override {
+ assert(mCompilationPackage && "package name not set");
+ return mCompilationPackage.value();
+ }
- uint8_t getPackageId() override {
- assert(mPackageId && "package ID not set");
- return mPackageId.value();
- }
+ uint8_t getPackageId() override {
+ assert(mPackageId && "package ID not set");
+ return mPackageId.value();
+ }
- NameMangler* getNameMangler() override {
- return &mNameMangler;
- }
+ NameMangler* getNameMangler() override { return &mNameMangler; }
- bool verbose() override {
- return false;
- }
+ bool verbose() override { return false; }
- int getMinSdkVersion() override {
- return mMinSdkVersion;
- }
+ int getMinSdkVersion() override { return mMinSdkVersion; }
-private:
- friend class ContextBuilder;
+ private:
+ friend class ContextBuilder;
- Maybe<std::string> mCompilationPackage;
- Maybe<uint8_t> mPackageId;
- StdErrDiagnostics mDiagnostics;
- SymbolTable mSymbols;
- NameMangler mNameMangler = NameMangler({});
- int mMinSdkVersion = 0;
+ Maybe<std::string> mCompilationPackage;
+ Maybe<uint8_t> mPackageId;
+ StdErrDiagnostics mDiagnostics;
+ SymbolTable mSymbols;
+ NameMangler mNameMangler = NameMangler({});
+ int mMinSdkVersion = 0;
};
class ContextBuilder {
-private:
- std::unique_ptr<Context> mContext = std::unique_ptr<Context>(new Context());
+ private:
+ std::unique_ptr<Context> mContext = std::unique_ptr<Context>(new Context());
-public:
- ContextBuilder& setCompilationPackage(const StringPiece& package) {
- mContext->mCompilationPackage = package.toString();
- return *this;
- }
+ public:
+ ContextBuilder& setCompilationPackage(const StringPiece& package) {
+ mContext->mCompilationPackage = package.toString();
+ return *this;
+ }
- ContextBuilder& setPackageId(uint8_t id) {
- mContext->mPackageId = id;
- return *this;
- }
+ ContextBuilder& setPackageId(uint8_t id) {
+ mContext->mPackageId = id;
+ return *this;
+ }
- ContextBuilder& setNameManglerPolicy(const NameManglerPolicy& policy) {
- mContext->mNameMangler = NameMangler(policy);
- return *this;
- }
+ ContextBuilder& setNameManglerPolicy(const NameManglerPolicy& policy) {
+ mContext->mNameMangler = NameMangler(policy);
+ return *this;
+ }
- ContextBuilder& addSymbolSource(std::unique_ptr<ISymbolSource> src) {
- mContext->getExternalSymbols()->appendSource(std::move(src));
- return *this;
- }
+ ContextBuilder& addSymbolSource(std::unique_ptr<ISymbolSource> src) {
+ mContext->getExternalSymbols()->appendSource(std::move(src));
+ return *this;
+ }
- ContextBuilder& setMinSdkVersion(int minSdk) {
- mContext->mMinSdkVersion = minSdk;
- return *this;
- }
+ ContextBuilder& setMinSdkVersion(int minSdk) {
+ mContext->mMinSdkVersion = minSdk;
+ return *this;
+ }
- std::unique_ptr<Context> build() {
- return std::move(mContext);
- }
+ std::unique_ptr<Context> build() { return std::move(mContext); }
};
class StaticSymbolSourceBuilder {
-public:
- StaticSymbolSourceBuilder& addPublicSymbol(const StringPiece& name, ResourceId id,
- std::unique_ptr<Attribute> attr = {}) {
- std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>(
- id, std::move(attr), true);
- mSymbolSource->mNameMap[parseNameOrDie(name)] = symbol.get();
- mSymbolSource->mIdMap[id] = symbol.get();
- mSymbolSource->mSymbols.push_back(std::move(symbol));
- return *this;
+ public:
+ StaticSymbolSourceBuilder& addPublicSymbol(
+ const StringPiece& name, ResourceId id,
+ std::unique_ptr<Attribute> attr = {}) {
+ std::unique_ptr<SymbolTable::Symbol> symbol =
+ util::make_unique<SymbolTable::Symbol>(id, std::move(attr), true);
+ mSymbolSource->mNameMap[parseNameOrDie(name)] = symbol.get();
+ mSymbolSource->mIdMap[id] = symbol.get();
+ mSymbolSource->mSymbols.push_back(std::move(symbol));
+ return *this;
+ }
+
+ StaticSymbolSourceBuilder& addSymbol(const StringPiece& name, ResourceId id,
+ std::unique_ptr<Attribute> attr = {}) {
+ std::unique_ptr<SymbolTable::Symbol> symbol =
+ util::make_unique<SymbolTable::Symbol>(id, std::move(attr), false);
+ mSymbolSource->mNameMap[parseNameOrDie(name)] = symbol.get();
+ mSymbolSource->mIdMap[id] = symbol.get();
+ mSymbolSource->mSymbols.push_back(std::move(symbol));
+ return *this;
+ }
+
+ std::unique_ptr<ISymbolSource> build() { return std::move(mSymbolSource); }
+
+ private:
+ class StaticSymbolSource : public ISymbolSource {
+ public:
+ StaticSymbolSource() = default;
+
+ std::unique_ptr<SymbolTable::Symbol> findByName(
+ const ResourceName& name) override {
+ auto iter = mNameMap.find(name);
+ if (iter != mNameMap.end()) {
+ return cloneSymbol(iter->second);
+ }
+ return nullptr;
}
- StaticSymbolSourceBuilder& addSymbol(const StringPiece& name, ResourceId id,
- std::unique_ptr<Attribute> attr = {}) {
- std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>(
- id, std::move(attr), false);
- mSymbolSource->mNameMap[parseNameOrDie(name)] = symbol.get();
- mSymbolSource->mIdMap[id] = symbol.get();
- mSymbolSource->mSymbols.push_back(std::move(symbol));
- return *this;
+ std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override {
+ auto iter = mIdMap.find(id);
+ if (iter != mIdMap.end()) {
+ return cloneSymbol(iter->second);
+ }
+ return nullptr;
}
- std::unique_ptr<ISymbolSource> build() {
- return std::move(mSymbolSource);
+ std::list<std::unique_ptr<SymbolTable::Symbol>> mSymbols;
+ std::map<ResourceName, SymbolTable::Symbol*> mNameMap;
+ std::map<ResourceId, SymbolTable::Symbol*> mIdMap;
+
+ private:
+ std::unique_ptr<SymbolTable::Symbol> cloneSymbol(SymbolTable::Symbol* sym) {
+ std::unique_ptr<SymbolTable::Symbol> clone =
+ util::make_unique<SymbolTable::Symbol>();
+ clone->id = sym->id;
+ if (sym->attribute) {
+ clone->attribute =
+ std::unique_ptr<Attribute>(sym->attribute->clone(nullptr));
+ }
+ clone->isPublic = sym->isPublic;
+ return clone;
}
-private:
- class StaticSymbolSource : public ISymbolSource {
- public:
- StaticSymbolSource() = default;
+ DISALLOW_COPY_AND_ASSIGN(StaticSymbolSource);
+ };
- std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) override {
- auto iter = mNameMap.find(name);
- if (iter != mNameMap.end()) {
- return cloneSymbol(iter->second);
- }
- return nullptr;
- }
-
- std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override {
- auto iter = mIdMap.find(id);
- if (iter != mIdMap.end()) {
- return cloneSymbol(iter->second);
- }
- return nullptr;
- }
-
- std::list<std::unique_ptr<SymbolTable::Symbol>> mSymbols;
- std::map<ResourceName, SymbolTable::Symbol*> mNameMap;
- std::map<ResourceId, SymbolTable::Symbol*> mIdMap;
-
- private:
- std::unique_ptr<SymbolTable::Symbol> cloneSymbol(SymbolTable::Symbol* sym) {
- std::unique_ptr<SymbolTable::Symbol> clone = util::make_unique<SymbolTable::Symbol>();
- clone->id = sym->id;
- if (sym->attribute) {
- clone->attribute = std::unique_ptr<Attribute>(sym->attribute->clone(nullptr));
- }
- clone->isPublic = sym->isPublic;
- return clone;
- }
-
- DISALLOW_COPY_AND_ASSIGN(StaticSymbolSource);
- };
-
- std::unique_ptr<StaticSymbolSource> mSymbolSource = util::make_unique<StaticSymbolSource>();
+ std::unique_ptr<StaticSymbolSource> mSymbolSource =
+ util::make_unique<StaticSymbolSource>();
};
-} // namespace test
-} // namespace aapt
+} // namespace test
+} // namespace aapt
#endif /* AAPT_TEST_CONTEXT_H */
diff --git a/tools/aapt2/test/Test.h b/tools/aapt2/test/Test.h
index d4845cf..c9188bf 100644
--- a/tools/aapt2/test/Test.h
+++ b/tools/aapt2/test/Test.h
@@ -24,9 +24,7 @@
#include <gtest/gtest.h>
namespace aapt {
-namespace test {
+namespace test {} // namespace test
+} // namespace aapt
-} // namespace test
-} // namespace aapt
-
-#endif // AAPT_TEST_TEST_H
+#endif // AAPT_TEST_TEST_H
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 4fd77c8..546b607 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -14,18 +14,18 @@
* limitations under the License.
*/
+#include "unflatten/BinaryResourceParser.h"
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
#include "Source.h"
#include "ValueVisitor.h"
-#include "unflatten/BinaryResourceParser.h"
#include "unflatten/ResChunkPullParser.h"
#include "util/Util.h"
+#include <android-base/macros.h>
#include <androidfw/ResourceTypes.h>
#include <androidfw/TypeWrappers.h>
-#include <android-base/macros.h>
#include <algorithm>
#include <map>
#include <string>
@@ -41,533 +41,553 @@
* given a mapping from resource ID to resource name.
*/
class ReferenceIdToNameVisitor : public ValueVisitor {
-private:
- const std::map<ResourceId, ResourceName>* mMapping;
+ private:
+ const std::map<ResourceId, ResourceName>* mMapping;
-public:
- using ValueVisitor::visit;
+ public:
+ using ValueVisitor::visit;
- explicit ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping) :
- mMapping(mapping) {
- assert(mMapping);
+ explicit ReferenceIdToNameVisitor(
+ const std::map<ResourceId, ResourceName>* mapping)
+ : mMapping(mapping) {
+ assert(mMapping);
+ }
+
+ void visit(Reference* reference) override {
+ if (!reference->id || !reference->id.value().isValid()) {
+ return;
}
- void visit(Reference* reference) override {
- if (!reference->id || !reference->id.value().isValid()) {
- return;
- }
-
- ResourceId id = reference->id.value();
- auto cacheIter = mMapping->find(id);
- if (cacheIter != mMapping->end()) {
- reference->name = cacheIter->second;
- }
+ ResourceId id = reference->id.value();
+ auto cacheIter = mMapping->find(id);
+ if (cacheIter != mMapping->end()) {
+ reference->name = cacheIter->second;
}
+ }
};
-} // namespace
+} // namespace
-BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table,
- const Source& source, const void* data, size_t len) :
- mContext(context), mTable(table), mSource(source), mData(data), mDataLen(len) {
-}
+BinaryResourceParser::BinaryResourceParser(IAaptContext* context,
+ ResourceTable* table,
+ const Source& source,
+ const void* data, size_t len)
+ : mContext(context),
+ mTable(table),
+ mSource(source),
+ mData(data),
+ mDataLen(len) {}
bool BinaryResourceParser::parse() {
- ResChunkPullParser parser(mData, mDataLen);
+ ResChunkPullParser parser(mData, mDataLen);
- bool error = false;
- while(ResChunkPullParser::isGoodEvent(parser.next())) {
- if (parser.getChunk()->type != android::RES_TABLE_TYPE) {
- mContext->getDiagnostics()->warn(DiagMessage(mSource)
- << "unknown chunk of type '"
- << (int) parser.getChunk()->type << "'");
- continue;
- }
-
- if (!parseTable(parser.getChunk())) {
- error = true;
- }
+ bool error = false;
+ while (ResChunkPullParser::isGoodEvent(parser.next())) {
+ if (parser.getChunk()->type != android::RES_TABLE_TYPE) {
+ mContext->getDiagnostics()->warn(DiagMessage(mSource)
+ << "unknown chunk of type '"
+ << (int)parser.getChunk()->type << "'");
+ continue;
}
- if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt resource table: "
- << parser.getLastError());
- return false;
+ if (!parseTable(parser.getChunk())) {
+ error = true;
}
- return !error;
+ }
+
+ if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt resource table: "
+ << parser.getLastError());
+ return false;
+ }
+ return !error;
}
/**
- * Parses the resource table, which contains all the packages, types, and entries.
+ * Parses the resource table, which contains all the packages, types, and
+ * entries.
*/
bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) {
- const ResTable_header* tableHeader = convertTo<ResTable_header>(chunk);
- if (!tableHeader) {
- mContext->getDiagnostics()->error(DiagMessage(mSource) << "corrupt ResTable_header chunk");
- return false;
- }
+ const ResTable_header* tableHeader = convertTo<ResTable_header>(chunk);
+ if (!tableHeader) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt ResTable_header chunk");
+ return false;
+ }
- ResChunkPullParser parser(getChunkData(&tableHeader->header),
- getChunkDataLen(&tableHeader->header));
- while (ResChunkPullParser::isGoodEvent(parser.next())) {
- switch (util::deviceToHost16(parser.getChunk()->type)) {
- case android::RES_STRING_POOL_TYPE:
- if (mValuePool.getError() == NO_INIT) {
- status_t err = mValuePool.setTo(parser.getChunk(),
- util::deviceToHost32(parser.getChunk()->size));
- if (err != NO_ERROR) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt string pool in ResTable: "
- << mValuePool.getError());
- return false;
- }
+ ResChunkPullParser parser(getChunkData(&tableHeader->header),
+ getChunkDataLen(&tableHeader->header));
+ while (ResChunkPullParser::isGoodEvent(parser.next())) {
+ switch (util::deviceToHost16(parser.getChunk()->type)) {
+ case android::RES_STRING_POOL_TYPE:
+ if (mValuePool.getError() == NO_INIT) {
+ status_t err = mValuePool.setTo(
+ parser.getChunk(), util::deviceToHost32(parser.getChunk()->size));
+ if (err != NO_ERROR) {
+ mContext->getDiagnostics()->error(
+ DiagMessage(mSource) << "corrupt string pool in ResTable: "
+ << mValuePool.getError());
+ return false;
+ }
- // Reserve some space for the strings we are going to add.
- mTable->stringPool.hintWillAdd(mValuePool.size(), mValuePool.styleCount());
- } else {
- mContext->getDiagnostics()->warn(DiagMessage(mSource)
- << "unexpected string pool in ResTable");
- }
- break;
-
- case android::RES_TABLE_PACKAGE_TYPE:
- if (!parsePackage(parser.getChunk())) {
- return false;
- }
- break;
-
- default:
- mContext->getDiagnostics()
- ->warn(DiagMessage(mSource)
- << "unexpected chunk type "
- << (int) util::deviceToHost16(parser.getChunk()->type));
- break;
+ // Reserve some space for the strings we are going to add.
+ mTable->stringPool.hintWillAdd(mValuePool.size(),
+ mValuePool.styleCount());
+ } else {
+ mContext->getDiagnostics()->warn(
+ DiagMessage(mSource) << "unexpected string pool in ResTable");
}
- }
+ break;
- if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt resource table: " << parser.getLastError());
- return false;
+ case android::RES_TABLE_PACKAGE_TYPE:
+ if (!parsePackage(parser.getChunk())) {
+ return false;
+ }
+ break;
+
+ default:
+ mContext->getDiagnostics()->warn(
+ DiagMessage(mSource)
+ << "unexpected chunk type "
+ << (int)util::deviceToHost16(parser.getChunk()->type));
+ break;
}
- return true;
+ }
+
+ if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt resource table: "
+ << parser.getLastError());
+ return false;
+ }
+ return true;
}
-
bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) {
- const ResTable_package* packageHeader = convertTo<ResTable_package>(chunk);
- if (!packageHeader) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt ResTable_package chunk");
- return false;
- }
+ const ResTable_package* packageHeader = convertTo<ResTable_package>(chunk);
+ if (!packageHeader) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt ResTable_package chunk");
+ return false;
+ }
- uint32_t packageId = util::deviceToHost32(packageHeader->id);
- if (packageId > std::numeric_limits<uint8_t>::max()) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "package ID is too big (" << packageId << ")");
- return false;
- }
+ uint32_t packageId = util::deviceToHost32(packageHeader->id);
+ if (packageId > std::numeric_limits<uint8_t>::max()) {
+ mContext->getDiagnostics()->error(
+ DiagMessage(mSource) << "package ID is too big (" << packageId << ")");
+ return false;
+ }
- // Extract the package name.
- size_t len = strnlen16((const char16_t*) packageHeader->name, arraysize(packageHeader->name));
- std::u16string packageName;
- packageName.resize(len);
- for (size_t i = 0; i < len; i++) {
- packageName[i] = util::deviceToHost16(packageHeader->name[i]);
- }
+ // Extract the package name.
+ size_t len = strnlen16((const char16_t*)packageHeader->name,
+ arraysize(packageHeader->name));
+ std::u16string packageName;
+ packageName.resize(len);
+ for (size_t i = 0; i < len; i++) {
+ packageName[i] = util::deviceToHost16(packageHeader->name[i]);
+ }
- ResourceTablePackage* package = mTable->createPackage(util::utf16ToUtf8(packageName),
- static_cast<uint8_t>(packageId));
- if (!package) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "incompatible package '" << packageName
- << "' with ID " << packageId);
- return false;
- }
+ ResourceTablePackage* package = mTable->createPackage(
+ util::utf16ToUtf8(packageName), static_cast<uint8_t>(packageId));
+ if (!package) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "incompatible package '" << packageName
+ << "' with ID " << packageId);
+ 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();
+ // 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())) {
- switch (util::deviceToHost16(parser.getChunk()->type)) {
- case android::RES_STRING_POOL_TYPE:
- if (mTypePool.getError() == NO_INIT) {
- status_t err = mTypePool.setTo(parser.getChunk(),
- util::deviceToHost32(parser.getChunk()->size));
- if (err != NO_ERROR) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt type string pool in "
- << "ResTable_package: "
- << mTypePool.getError());
- return false;
- }
- } else if (mKeyPool.getError() == NO_INIT) {
- status_t err = mKeyPool.setTo(parser.getChunk(),
- util::deviceToHost32(parser.getChunk()->size));
- if (err != NO_ERROR) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt key string pool in "
- << "ResTable_package: "
- << mKeyPool.getError());
- return false;
- }
- } else {
- mContext->getDiagnostics()->warn(DiagMessage(mSource) << "unexpected string pool");
- }
- break;
-
- case android::RES_TABLE_TYPE_SPEC_TYPE:
- if (!parseTypeSpec(parser.getChunk())) {
- return false;
- }
- break;
-
- case android::RES_TABLE_TYPE_TYPE:
- if (!parseType(package, parser.getChunk())) {
- return false;
- }
- break;
-
- default:
- mContext->getDiagnostics()
- ->warn(DiagMessage(mSource)
- << "unexpected chunk type "
- << (int) util::deviceToHost16(parser.getChunk()->type));
- break;
+ ResChunkPullParser parser(getChunkData(&packageHeader->header),
+ getChunkDataLen(&packageHeader->header));
+ while (ResChunkPullParser::isGoodEvent(parser.next())) {
+ switch (util::deviceToHost16(parser.getChunk()->type)) {
+ case android::RES_STRING_POOL_TYPE:
+ if (mTypePool.getError() == NO_INIT) {
+ status_t err = mTypePool.setTo(
+ parser.getChunk(), util::deviceToHost32(parser.getChunk()->size));
+ if (err != NO_ERROR) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt type string pool in "
+ << "ResTable_package: "
+ << mTypePool.getError());
+ return false;
+ }
+ } else if (mKeyPool.getError() == NO_INIT) {
+ status_t err = mKeyPool.setTo(
+ parser.getChunk(), util::deviceToHost32(parser.getChunk()->size));
+ if (err != NO_ERROR) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt key string pool in "
+ << "ResTable_package: "
+ << mKeyPool.getError());
+ return false;
+ }
+ } else {
+ mContext->getDiagnostics()->warn(DiagMessage(mSource)
+ << "unexpected string pool");
}
- }
+ break;
- if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt ResTable_package: "
- << parser.getLastError());
- return false;
- }
+ case android::RES_TABLE_TYPE_SPEC_TYPE:
+ if (!parseTypeSpec(parser.getChunk())) {
+ return false;
+ }
+ break;
- // Now go through the table and change local resource ID references to
- // symbolic references.
- ReferenceIdToNameVisitor visitor(&mIdIndex);
- visitAllValuesInTable(mTable, &visitor);
- return true;
+ case android::RES_TABLE_TYPE_TYPE:
+ if (!parseType(package, parser.getChunk())) {
+ return false;
+ }
+ break;
+
+ default:
+ mContext->getDiagnostics()->warn(
+ DiagMessage(mSource)
+ << "unexpected chunk type "
+ << (int)util::deviceToHost16(parser.getChunk()->type));
+ break;
+ }
+ }
+
+ if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt ResTable_package: "
+ << parser.getLastError());
+ return false;
+ }
+
+ // Now go through the table and change local resource ID references to
+ // symbolic references.
+ ReferenceIdToNameVisitor visitor(&mIdIndex);
+ visitAllValuesInTable(mTable, &visitor);
+ return true;
}
bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) {
- if (mTypePool.getError() != NO_ERROR) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "missing type string pool");
- return false;
- }
+ if (mTypePool.getError() != NO_ERROR) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "missing type string pool");
+ return false;
+ }
- const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk);
- if (!typeSpec) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt ResTable_typeSpec chunk");
- return false;
- }
+ const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk);
+ if (!typeSpec) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt ResTable_typeSpec chunk");
+ return false;
+ }
- if (typeSpec->id == 0) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "ResTable_typeSpec has invalid id: " << typeSpec->id);
- return false;
- }
- return true;
+ if (typeSpec->id == 0) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "ResTable_typeSpec has invalid id: "
+ << typeSpec->id);
+ return false;
+ }
+ return true;
}
bool BinaryResourceParser::parseType(const ResourceTablePackage* package,
const ResChunk_header* chunk) {
- if (mTypePool.getError() != NO_ERROR) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "missing type string pool");
+ if (mTypePool.getError() != NO_ERROR) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "missing type string pool");
+ return false;
+ }
+
+ if (mKeyPool.getError() != NO_ERROR) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "missing key string pool");
+ return false;
+ }
+
+ const ResTable_type* type = convertTo<ResTable_type>(chunk);
+ if (!type) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "corrupt ResTable_type chunk");
+ return false;
+ }
+
+ if (type->id == 0) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "ResTable_type has invalid id: "
+ << (int)type->id);
+ return false;
+ }
+
+ ConfigDescription config;
+ config.copyFromDtoH(type->config);
+
+ const std::string typeStr = util::getString(mTypePool, type->id - 1);
+
+ const ResourceType* parsedType = parseResourceType(typeStr);
+ if (!parsedType) {
+ mContext->getDiagnostics()->error(
+ DiagMessage(mSource) << "invalid type name '" << typeStr
+ << "' for type with ID " << (int)type->id);
+ return false;
+ }
+
+ TypeVariant tv(type);
+ for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) {
+ const ResTable_entry* entry = *it;
+ if (!entry) {
+ continue;
+ }
+
+ const ResourceName name(
+ package->name, *parsedType,
+ util::getString(mKeyPool, util::deviceToHost32(entry->key.index)));
+
+ const ResourceId resId(package->id.value(), type->id,
+ static_cast<uint16_t>(it.index()));
+
+ std::unique_ptr<Value> resourceValue;
+ if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
+ const ResTable_map_entry* mapEntry =
+ static_cast<const ResTable_map_entry*>(entry);
+
+ // TODO(adamlesinski): Check that the entry count is valid.
+ resourceValue = parseMapEntry(name, config, mapEntry);
+ } else {
+ const Res_value* value =
+ (const Res_value*)((const uint8_t*)entry +
+ util::deviceToHost32(entry->size));
+ resourceValue = parseValue(name, config, value, entry->flags);
+ }
+
+ if (!resourceValue) {
+ mContext->getDiagnostics()->error(
+ DiagMessage(mSource) << "failed to parse value for resource " << name
+ << " (" << resId << ") with configuration '"
+ << config << "'");
+ return false;
+ }
+
+ if (!mTable->addResourceAllowMangled(name, config, {},
+ std::move(resourceValue),
+ mContext->getDiagnostics())) {
+ return false;
+ }
+
+ if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
+ Symbol symbol;
+ symbol.state = SymbolState::kPublic;
+ symbol.source = mSource.withLine(0);
+ if (!mTable->setSymbolStateAllowMangled(name, resId, symbol,
+ mContext->getDiagnostics())) {
return false;
+ }
}
- if (mKeyPool.getError() != NO_ERROR) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "missing key string pool");
- return false;
+ // Add this resource name->id mapping to the index so
+ // that we can resolve all ID references to name references.
+ auto cacheIter = mIdIndex.find(resId);
+ if (cacheIter == mIdIndex.end()) {
+ mIdIndex.insert({resId, name});
}
-
- const ResTable_type* type = convertTo<ResTable_type>(chunk);
- if (!type) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt ResTable_type chunk");
- return false;
- }
-
- if (type->id == 0) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "ResTable_type has invalid id: " << (int) type->id);
- return false;
- }
-
- ConfigDescription config;
- config.copyFromDtoH(type->config);
-
- const std::string typeStr = util::getString(mTypePool, type->id - 1);
-
- const ResourceType* parsedType = parseResourceType(typeStr);
- if (!parsedType) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "invalid type name '" << typeStr
- << "' for type with ID " << (int) type->id);
- return false;
- }
-
- TypeVariant tv(type);
- for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) {
- const ResTable_entry* entry = *it;
- if (!entry) {
- continue;
- }
-
- const ResourceName name(package->name, *parsedType,
- util::getString(mKeyPool,
- util::deviceToHost32(entry->key.index)));
-
- const ResourceId resId(package->id.value(), type->id, static_cast<uint16_t>(it.index()));
-
- std::unique_ptr<Value> resourceValue;
- if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
- const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
-
- // TODO(adamlesinski): Check that the entry count is valid.
- resourceValue = parseMapEntry(name, config, mapEntry);
- } else {
- const Res_value* value = (const Res_value*)(
- (const uint8_t*) entry + util::deviceToHost32(entry->size));
- resourceValue = parseValue(name, config, value, entry->flags);
- }
-
- if (!resourceValue) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "failed to parse value for resource " << name
- << " (" << resId << ") with configuration '"
- << config << "'");
- return false;
- }
-
- if (!mTable->addResourceAllowMangled(name, config, {}, std::move(resourceValue),
- mContext->getDiagnostics())) {
- return false;
- }
-
- if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
- Symbol symbol;
- symbol.state = SymbolState::kPublic;
- symbol.source = mSource.withLine(0);
- if (!mTable->setSymbolStateAllowMangled(name, resId, symbol,
- mContext->getDiagnostics())) {
- return false;
- }
- }
-
- // Add this resource name->id mapping to the index so
- // that we can resolve all ID references to name references.
- auto cacheIter = mIdIndex.find(resId);
- if (cacheIter == mIdIndex.end()) {
- mIdIndex.insert({ resId, name });
- }
- }
- return true;
+ }
+ return true;
}
-std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& name,
- const ConfigDescription& config,
- const Res_value* value,
- uint16_t flags) {
- if (name.type == ResourceType::kId) {
- return util::make_unique<Id>();
+std::unique_ptr<Item> BinaryResourceParser::parseValue(
+ const ResourceNameRef& name, const ConfigDescription& config,
+ const Res_value* value, uint16_t flags) {
+ if (name.type == ResourceType::kId) {
+ return util::make_unique<Id>();
+ }
+
+ const uint32_t data = util::deviceToHost32(value->data);
+
+ if (value->dataType == Res_value::TYPE_STRING) {
+ const std::string str = util::getString(mValuePool, data);
+
+ const ResStringPool_span* spans = mValuePool.styleAt(data);
+
+ // Check if the string has a valid style associated with it.
+ if (spans != nullptr && spans->name.index != ResStringPool_span::END) {
+ StyleString styleStr = {str};
+ while (spans->name.index != ResStringPool_span::END) {
+ styleStr.spans.push_back(
+ Span{util::getString(mValuePool, spans->name.index),
+ spans->firstChar, spans->lastChar});
+ spans++;
+ }
+ return util::make_unique<StyledString>(mTable->stringPool.makeRef(
+ styleStr,
+ StringPool::Context(StringPool::Context::kStylePriority, config)));
+ } else {
+ if (name.type != ResourceType::kString &&
+ util::stringStartsWith(str, "res/")) {
+ // This must be a FileReference.
+ return util::make_unique<FileReference>(mTable->stringPool.makeRef(
+ str,
+ StringPool::Context(StringPool::Context::kHighPriority, config)));
+ }
+
+ // There are no styles associated with this string, so treat it as
+ // a simple string.
+ return util::make_unique<String>(
+ mTable->stringPool.makeRef(str, StringPool::Context(config)));
+ }
+ }
+
+ if (value->dataType == Res_value::TYPE_REFERENCE ||
+ value->dataType == Res_value::TYPE_ATTRIBUTE) {
+ const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE)
+ ? Reference::Type::kResource
+ : Reference::Type::kAttribute;
+
+ if (data == 0) {
+ // A reference of 0, must be the magic @null reference.
+ Res_value nullType = {};
+ nullType.dataType = Res_value::TYPE_REFERENCE;
+ return util::make_unique<BinaryPrimitive>(nullType);
}
- const uint32_t data = util::deviceToHost32(value->data);
+ // This is a normal reference.
+ return util::make_unique<Reference>(data, type);
+ }
- if (value->dataType == Res_value::TYPE_STRING) {
- const std::string str = util::getString(mValuePool, data);
-
- const ResStringPool_span* spans = mValuePool.styleAt(data);
-
- // Check if the string has a valid style associated with it.
- if (spans != nullptr && spans->name.index != ResStringPool_span::END) {
- StyleString styleStr = { str };
- while (spans->name.index != ResStringPool_span::END) {
- styleStr.spans.push_back(Span{
- util::getString(mValuePool, spans->name.index),
- spans->firstChar,
- spans->lastChar
- });
- spans++;
- }
- return util::make_unique<StyledString>(mTable->stringPool.makeRef(
- styleStr, StringPool::Context{1, config}));
- } else {
- if (name.type != ResourceType::kString &&
- util::stringStartsWith(str, "res/")) {
- // This must be a FileReference.
- return util::make_unique<FileReference>(mTable->stringPool.makeRef(
- str, StringPool::Context{ 0, config }));
- }
-
- // There are no styles associated with this string, so treat it as
- // a simple string.
- return util::make_unique<String>(mTable->stringPool.makeRef(
- str, StringPool::Context{1, config}));
- }
- }
-
- if (value->dataType == Res_value::TYPE_REFERENCE ||
- value->dataType == Res_value::TYPE_ATTRIBUTE) {
- const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ?
- Reference::Type::kResource : Reference::Type::kAttribute;
-
- if (data == 0) {
- // A reference of 0, must be the magic @null reference.
- Res_value nullType = {};
- nullType.dataType = Res_value::TYPE_REFERENCE;
- return util::make_unique<BinaryPrimitive>(nullType);
- }
-
- // This is a normal reference.
- return util::make_unique<Reference>(data, type);
- }
-
- // Treat this as a raw binary primitive.
- return util::make_unique<BinaryPrimitive>(*value);
+ // Treat this as a raw binary primitive.
+ return util::make_unique<BinaryPrimitive>(*value);
}
-std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef& name,
- const ConfigDescription& config,
- const ResTable_map_entry* map) {
- switch (name.type) {
- case ResourceType::kStyle:
- return parseStyle(name, config, map);
- case ResourceType::kAttrPrivate:
- // fallthrough
- case ResourceType::kAttr:
- return parseAttr(name, config, map);
- case ResourceType::kArray:
- return parseArray(name, config, map);
- case ResourceType::kPlurals:
- return parsePlural(name, config, map);
- default:
- assert(false && "unknown map type");
- break;
- }
- return {};
+std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(
+ const ResourceNameRef& name, const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ switch (name.type) {
+ case ResourceType::kStyle:
+ return parseStyle(name, config, map);
+ case ResourceType::kAttrPrivate:
+ // fallthrough
+ case ResourceType::kAttr:
+ return parseAttr(name, config, map);
+ case ResourceType::kArray:
+ return parseArray(name, config, map);
+ case ResourceType::kPlurals:
+ return parsePlural(name, config, map);
+ default:
+ assert(false && "unknown map type");
+ break;
+ }
+ return {};
}
-std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name,
- const ConfigDescription& config,
- const ResTable_map_entry* map) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
- if (util::deviceToHost32(map->parent.ident) != 0) {
- // The parent is a regular reference to a resource.
- style->parent = Reference(util::deviceToHost32(map->parent.ident));
+std::unique_ptr<Style> BinaryResourceParser::parseStyle(
+ const ResourceNameRef& name, const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+ if (util::deviceToHost32(map->parent.ident) != 0) {
+ // The parent is a regular reference to a resource.
+ style->parent = Reference(util::deviceToHost32(map->parent.ident));
+ }
+
+ for (const ResTable_map& mapEntry : map) {
+ if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
+ continue;
}
- for (const ResTable_map& mapEntry : map) {
- if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
- continue;
- }
-
- Style::Entry styleEntry;
- styleEntry.key = Reference(util::deviceToHost32(mapEntry.name.ident));
- styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
- if (!styleEntry.value) {
- return {};
- }
- style->entries.push_back(std::move(styleEntry));
+ Style::Entry styleEntry;
+ styleEntry.key = Reference(util::deviceToHost32(mapEntry.name.ident));
+ styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
+ if (!styleEntry.value) {
+ return {};
}
- return style;
+ style->entries.push_back(std::move(styleEntry));
+ }
+ return style;
}
-std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef& name,
- const ConfigDescription& config,
- const ResTable_map_entry* map) {
- const bool isWeak = (util::deviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0;
- std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
+std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(
+ const ResourceNameRef& name, const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ const bool isWeak =
+ (util::deviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0;
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
- // First we must discover what type of attribute this is. Find the type mask.
- auto typeMaskIter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
- return util::deviceToHost32(entry.name.ident) == ResTable_map::ATTR_TYPE;
- });
+ // First we must discover what type of attribute this is. Find the type mask.
+ auto typeMaskIter =
+ std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
+ return util::deviceToHost32(entry.name.ident) ==
+ ResTable_map::ATTR_TYPE;
+ });
- if (typeMaskIter != end(map)) {
- attr->typeMask = util::deviceToHost32(typeMaskIter->value.data);
+ if (typeMaskIter != end(map)) {
+ attr->typeMask = util::deviceToHost32(typeMaskIter->value.data);
+ }
+
+ for (const ResTable_map& mapEntry : map) {
+ if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
+ switch (util::deviceToHost32(mapEntry.name.ident)) {
+ case ResTable_map::ATTR_MIN:
+ attr->minInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
+ case ResTable_map::ATTR_MAX:
+ attr->maxInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
+ }
+ continue;
}
- for (const ResTable_map& mapEntry : map) {
- if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
- switch (util::deviceToHost32(mapEntry.name.ident)) {
- case ResTable_map::ATTR_MIN:
- attr->minInt = static_cast<int32_t>(mapEntry.value.data);
- break;
- case ResTable_map::ATTR_MAX:
- attr->maxInt = static_cast<int32_t>(mapEntry.value.data);
- break;
- }
- continue;
- }
-
- if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
- Attribute::Symbol symbol;
- symbol.value = util::deviceToHost32(mapEntry.value.data);
- symbol.symbol = Reference(util::deviceToHost32(mapEntry.name.ident));
- attr->symbols.push_back(std::move(symbol));
- }
+ if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
+ Attribute::Symbol symbol;
+ symbol.value = util::deviceToHost32(mapEntry.value.data);
+ symbol.symbol = Reference(util::deviceToHost32(mapEntry.name.ident));
+ attr->symbols.push_back(std::move(symbol));
}
+ }
- // TODO(adamlesinski): Find i80n, attributes.
- return attr;
+ // TODO(adamlesinski): Find i80n, attributes.
+ return attr;
}
-std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
- const ConfigDescription& config,
- const ResTable_map_entry* map) {
- std::unique_ptr<Array> array = util::make_unique<Array>();
- for (const ResTable_map& mapEntry : map) {
- array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
- }
- return array;
+std::unique_ptr<Array> BinaryResourceParser::parseArray(
+ const ResourceNameRef& name, const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ std::unique_ptr<Array> array = util::make_unique<Array>();
+ for (const ResTable_map& mapEntry : map) {
+ array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
+ }
+ return array;
}
-std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& name,
- const ConfigDescription& config,
- const ResTable_map_entry* map) {
- std::unique_ptr<Plural> plural = util::make_unique<Plural>();
- for (const ResTable_map& mapEntry : map) {
- std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
- if (!item) {
- return {};
- }
-
- switch (util::deviceToHost32(mapEntry.name.ident)) {
- case ResTable_map::ATTR_ZERO:
- plural->values[Plural::Zero] = std::move(item);
- break;
- case ResTable_map::ATTR_ONE:
- plural->values[Plural::One] = std::move(item);
- break;
- case ResTable_map::ATTR_TWO:
- plural->values[Plural::Two] = std::move(item);
- break;
- case ResTable_map::ATTR_FEW:
- plural->values[Plural::Few] = std::move(item);
- break;
- case ResTable_map::ATTR_MANY:
- plural->values[Plural::Many] = std::move(item);
- break;
- case ResTable_map::ATTR_OTHER:
- plural->values[Plural::Other] = std::move(item);
- break;
- }
+std::unique_ptr<Plural> BinaryResourceParser::parsePlural(
+ const ResourceNameRef& name, const ConfigDescription& config,
+ const ResTable_map_entry* map) {
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ for (const ResTable_map& mapEntry : map) {
+ std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
+ if (!item) {
+ return {};
}
- return plural;
+
+ switch (util::deviceToHost32(mapEntry.name.ident)) {
+ case ResTable_map::ATTR_ZERO:
+ plural->values[Plural::Zero] = std::move(item);
+ break;
+ case ResTable_map::ATTR_ONE:
+ plural->values[Plural::One] = std::move(item);
+ break;
+ case ResTable_map::ATTR_TWO:
+ plural->values[Plural::Two] = std::move(item);
+ break;
+ case ResTable_map::ATTR_FEW:
+ plural->values[Plural::Few] = std::move(item);
+ break;
+ case ResTable_map::ATTR_MANY:
+ plural->values[Plural::Many] = std::move(item);
+ break;
+ case ResTable_map::ATTR_OTHER:
+ plural->values[Plural::Other] = std::move(item);
+ break;
+ }
+ }
+ return plural;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index 12bc13d..99f2bd4 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -39,80 +39,86 @@
* chunks and types.
*/
class BinaryResourceParser {
-public:
- /*
- * Creates a parser, which will read `len` bytes from `data`, and
- * add any resources parsed to `table`. `source` is for logging purposes.
- */
- BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source,
- const void* data, size_t dataLen);
+ public:
+ /*
+ * Creates a parser, which will read `len` bytes from `data`, and
+ * add any resources parsed to `table`. `source` is for logging purposes.
+ */
+ BinaryResourceParser(IAaptContext* context, ResourceTable* table,
+ const Source& source, const void* data, size_t dataLen);
- BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy.
+ BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy.
- /*
- * Parses the binary resource table and returns true if successful.
- */
- bool parse();
+ /*
+ * Parses the binary resource table and returns true if successful.
+ */
+ bool parse();
-private:
- bool parseTable(const android::ResChunk_header* chunk);
- bool parsePackage(const android::ResChunk_header* chunk);
- bool parseTypeSpec(const android::ResChunk_header* chunk);
- bool parseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
+ private:
+ bool parseTable(const android::ResChunk_header* chunk);
+ bool parsePackage(const android::ResChunk_header* chunk);
+ bool parseTypeSpec(const android::ResChunk_header* chunk);
+ bool parseType(const ResourceTablePackage* package,
+ const android::ResChunk_header* chunk);
- std::unique_ptr<Item> parseValue(const ResourceNameRef& name, const ConfigDescription& config,
- const android::Res_value* value, uint16_t flags);
+ std::unique_ptr<Item> parseValue(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const android::Res_value* value,
+ uint16_t flags);
- std::unique_ptr<Value> parseMapEntry(const ResourceNameRef& name,
- const ConfigDescription& config,
- const android::ResTable_map_entry* map);
+ std::unique_ptr<Value> parseMapEntry(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
- std::unique_ptr<Style> parseStyle(const ResourceNameRef& name, const ConfigDescription& config,
+ std::unique_ptr<Style> parseStyle(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
+
+ std::unique_ptr<Attribute> parseAttr(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
+
+ std::unique_ptr<Array> parseArray(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
+
+ std::unique_ptr<Plural> parsePlural(const ResourceNameRef& name,
+ const ConfigDescription& config,
const android::ResTable_map_entry* map);
- std::unique_ptr<Attribute> parseAttr(const ResourceNameRef& name,
- const ConfigDescription& config,
- const android::ResTable_map_entry* map);
+ /**
+ * If the mapEntry is a special type that denotes meta data (source, comment),
+ * then it is
+ * read and added to the Value.
+ * Returns true if the mapEntry was meta data.
+ */
+ bool collectMetaData(const android::ResTable_map& mapEntry, Value* value);
- std::unique_ptr<Array> parseArray(const ResourceNameRef& name, const ConfigDescription& config,
- const android::ResTable_map_entry* map);
+ IAaptContext* mContext;
+ ResourceTable* mTable;
- std::unique_ptr<Plural> parsePlural(const ResourceNameRef& name,
- const ConfigDescription& config,
- const android::ResTable_map_entry* map);
+ const Source mSource;
- /**
- * If the mapEntry is a special type that denotes meta data (source, comment), then it is
- * read and added to the Value.
- * Returns true if the mapEntry was meta data.
- */
- bool collectMetaData(const android::ResTable_map& mapEntry, Value* value);
+ const void* mData;
+ const size_t mDataLen;
- IAaptContext* mContext;
- ResourceTable* mTable;
+ // The standard value string pool for resource values.
+ android::ResStringPool mValuePool;
- const Source mSource;
+ // The string pool that holds the names of the types defined
+ // in this table.
+ android::ResStringPool mTypePool;
- const void* mData;
- const size_t mDataLen;
+ // The string pool that holds the names of the entries defined
+ // in this table.
+ android::ResStringPool mKeyPool;
- // The standard value string pool for resource values.
- android::ResStringPool mValuePool;
-
- // The string pool that holds the names of the types defined
- // in this table.
- android::ResStringPool mTypePool;
-
- // The string pool that holds the names of the entries defined
- // in this table.
- android::ResStringPool mKeyPool;
-
- // A mapping of resource ID to resource name. When we finish parsing
- // we use this to convert all resource IDs to symbolic references.
- std::map<ResourceId, ResourceName> mIdIndex;
+ // A mapping of resource ID to resource name. When we finish parsing
+ // we use this to convert all resource IDs to symbolic references.
+ std::map<ResourceId, ResourceName> mIdIndex;
};
-} // namespace aapt
+} // namespace aapt
namespace android {
@@ -121,13 +127,14 @@
*/
inline const ResTable_map* begin(const ResTable_map_entry* map) {
- return (const ResTable_map*)((const uint8_t*) map + aapt::util::deviceToHost32(map->size));
+ return (const ResTable_map*)((const uint8_t*)map +
+ aapt::util::deviceToHost32(map->size));
}
inline const ResTable_map* end(const ResTable_map_entry* map) {
- return begin(map) + aapt::util::deviceToHost32(map->count);
+ return begin(map) + aapt::util::deviceToHost32(map->count);
}
-} // namespace android
+} // namespace android
-#endif // AAPT_BINARY_RESOURCE_PARSER_H
+#endif // AAPT_BINARY_RESOURCE_PARSER_H
diff --git a/tools/aapt2/unflatten/ResChunkPullParser.h b/tools/aapt2/unflatten/ResChunkPullParser.h
index a51d5bf..24fa63d 100644
--- a/tools/aapt2/unflatten/ResChunkPullParser.h
+++ b/tools/aapt2/unflatten/ResChunkPullParser.h
@@ -37,59 +37,62 @@
* pointing to the data portion of a chunk.
*/
class ResChunkPullParser {
-public:
- enum class Event {
- StartDocument,
- EndDocument,
- BadDocument,
+ public:
+ enum class Event {
+ StartDocument,
+ EndDocument,
+ BadDocument,
- Chunk,
- };
+ Chunk,
+ };
- /**
- * Returns false if the event is EndDocument or BadDocument.
- */
- static bool isGoodEvent(Event event);
+ /**
+ * Returns false if the event is EndDocument or BadDocument.
+ */
+ static bool isGoodEvent(Event event);
- /**
- * Create a ResChunkPullParser to read android::ResChunk_headers
- * from the memory pointed to by data, of len bytes.
- */
- ResChunkPullParser(const void* data, size_t len);
+ /**
+ * Create a ResChunkPullParser to read android::ResChunk_headers
+ * from the memory pointed to by data, of len bytes.
+ */
+ ResChunkPullParser(const void* data, size_t len);
- ResChunkPullParser(const ResChunkPullParser&) = delete;
+ ResChunkPullParser(const ResChunkPullParser&) = delete;
- Event getEvent() const;
- const std::string& getLastError() const;
- const android::ResChunk_header* getChunk() const;
+ Event getEvent() const;
+ const std::string& getLastError() const;
+ const android::ResChunk_header* getChunk() const;
- /**
- * Move to the next android::ResChunk_header.
- */
- Event next();
+ /**
+ * Move to the next android::ResChunk_header.
+ */
+ Event next();
-private:
- Event mEvent;
- const android::ResChunk_header* mData;
- size_t mLen;
- const android::ResChunk_header* mCurrentChunk;
- std::string mLastError;
+ private:
+ Event mEvent;
+ const android::ResChunk_header* mData;
+ size_t mLen;
+ const android::ResChunk_header* mCurrentChunk;
+ std::string mLastError;
};
template <typename T>
inline static const T* convertTo(const android::ResChunk_header* chunk) {
- if (util::deviceToHost16(chunk->headerSize) < sizeof(T)) {
- return nullptr;
- }
- return reinterpret_cast<const T*>(chunk);
+ if (util::deviceToHost16(chunk->headerSize) < sizeof(T)) {
+ return nullptr;
+ }
+ return reinterpret_cast<const T*>(chunk);
}
-inline static const uint8_t* getChunkData(const android::ResChunk_header* chunk) {
- return reinterpret_cast<const uint8_t*>(chunk) + util::deviceToHost16(chunk->headerSize);
+inline static const uint8_t* getChunkData(
+ const android::ResChunk_header* chunk) {
+ return reinterpret_cast<const uint8_t*>(chunk) +
+ util::deviceToHost16(chunk->headerSize);
}
inline static uint32_t getChunkDataLen(const android::ResChunk_header* chunk) {
- return util::deviceToHost32(chunk->size) - util::deviceToHost16(chunk->headerSize);
+ return util::deviceToHost32(chunk->size) -
+ util::deviceToHost16(chunk->headerSize);
}
//
@@ -97,28 +100,27 @@
//
inline bool ResChunkPullParser::isGoodEvent(ResChunkPullParser::Event event) {
- return event != Event::EndDocument && event != Event::BadDocument;
+ return event != Event::EndDocument && event != Event::BadDocument;
}
-inline ResChunkPullParser::ResChunkPullParser(const void* data, size_t len) :
- mEvent(Event::StartDocument),
- mData(reinterpret_cast<const android::ResChunk_header*>(data)),
- mLen(len),
- mCurrentChunk(nullptr) {
-}
+inline ResChunkPullParser::ResChunkPullParser(const void* data, size_t len)
+ : mEvent(Event::StartDocument),
+ mData(reinterpret_cast<const android::ResChunk_header*>(data)),
+ mLen(len),
+ mCurrentChunk(nullptr) {}
inline ResChunkPullParser::Event ResChunkPullParser::getEvent() const {
- return mEvent;
+ return mEvent;
}
inline const std::string& ResChunkPullParser::getLastError() const {
- return mLastError;
+ return mLastError;
}
inline const android::ResChunk_header* ResChunkPullParser::getChunk() const {
- return mCurrentChunk;
+ return mCurrentChunk;
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_RES_CHUNK_PULL_PARSER_H
+#endif // AAPT_RES_CHUNK_PULL_PARSER_H
diff --git a/tools/aapt2/util/BigBuffer.h b/tools/aapt2/util/BigBuffer.h
index 685614f..b273733 100644
--- a/tools/aapt2/util/BigBuffer.h
+++ b/tools/aapt2/util/BigBuffer.h
@@ -32,156 +32,153 @@
* block is allocated and appended to the end of the list.
*/
class BigBuffer {
-public:
+ public:
+ /**
+ * A contiguous block of allocated memory.
+ */
+ struct Block {
/**
- * A contiguous block of allocated memory.
+ * Pointer to the memory.
*/
- struct Block {
- /**
- * Pointer to the memory.
- */
- std::unique_ptr<uint8_t[]> buffer;
-
- /**
- * Size of memory that is currently occupied. The actual
- * allocation may be larger.
- */
- size_t size;
-
- private:
- friend class BigBuffer;
-
- /**
- * The size of the memory block allocation.
- */
- size_t mBlockSize;
- };
-
- typedef std::vector<Block>::const_iterator const_iterator;
+ std::unique_ptr<uint8_t[]> buffer;
/**
- * Create a BigBuffer with block allocation sizes
- * of blockSize.
+ * Size of memory that is currently occupied. The actual
+ * allocation may be larger.
*/
- explicit BigBuffer(size_t blockSize);
+ size_t size;
- BigBuffer(const BigBuffer&) = delete; // No copying.
-
- BigBuffer(BigBuffer&& rhs);
+ private:
+ friend class BigBuffer;
/**
- * Number of occupied bytes in all the allocated blocks.
+ * The size of the memory block allocation.
*/
- size_t size() const;
-
- /**
- * Returns a pointer to an array of T, where T is
- * a POD type. The elements are zero-initialized.
- */
- template <typename T>
- T* nextBlock(size_t count = 1);
-
- /**
- * Returns the next block available and puts the size in outCount.
- * This is useful for grabbing blocks where the size doesn't matter.
- * Use backUp() to give back any bytes that were not used.
- */
- void* nextBlock(size_t* outCount);
-
- /**
- * Backs up count bytes. This must only be called after nextBlock()
- * and can not be larger than sizeof(T) * count of the last nextBlock()
- * call.
- */
- void backUp(size_t count);
-
- /**
- * Moves the specified BigBuffer into this one. When this method
- * returns, buffer is empty.
- */
- void appendBuffer(BigBuffer&& buffer);
-
- /**
- * Pads the block with 'bytes' bytes of zero values.
- */
- void pad(size_t bytes);
-
- /**
- * Pads the block so that it aligns on a 4 byte boundary.
- */
- void align4();
-
- size_t getBlockSize() const;
-
- const_iterator begin() const;
- const_iterator end() const;
-
-private:
- /**
- * Returns a pointer to a buffer of the requested size.
- * The buffer is zero-initialized.
- */
- void* nextBlockImpl(size_t size);
-
size_t mBlockSize;
- size_t mSize;
- std::vector<Block> mBlocks;
+ };
+
+ typedef std::vector<Block>::const_iterator const_iterator;
+
+ /**
+ * Create a BigBuffer with block allocation sizes
+ * of blockSize.
+ */
+ explicit BigBuffer(size_t blockSize);
+
+ BigBuffer(const BigBuffer&) = delete; // No copying.
+
+ BigBuffer(BigBuffer&& rhs);
+
+ /**
+ * Number of occupied bytes in all the allocated blocks.
+ */
+ size_t size() const;
+
+ /**
+ * Returns a pointer to an array of T, where T is
+ * a POD type. The elements are zero-initialized.
+ */
+ template <typename T>
+ T* nextBlock(size_t count = 1);
+
+ /**
+ * Returns the next block available and puts the size in outCount.
+ * This is useful for grabbing blocks where the size doesn't matter.
+ * Use backUp() to give back any bytes that were not used.
+ */
+ void* nextBlock(size_t* outCount);
+
+ /**
+ * Backs up count bytes. This must only be called after nextBlock()
+ * and can not be larger than sizeof(T) * count of the last nextBlock()
+ * call.
+ */
+ void backUp(size_t count);
+
+ /**
+ * Moves the specified BigBuffer into this one. When this method
+ * returns, buffer is empty.
+ */
+ void appendBuffer(BigBuffer&& buffer);
+
+ /**
+ * Pads the block with 'bytes' bytes of zero values.
+ */
+ void pad(size_t bytes);
+
+ /**
+ * Pads the block so that it aligns on a 4 byte boundary.
+ */
+ void align4();
+
+ size_t getBlockSize() const;
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ private:
+ /**
+ * Returns a pointer to a buffer of the requested size.
+ * The buffer is zero-initialized.
+ */
+ void* nextBlockImpl(size_t size);
+
+ size_t mBlockSize;
+ size_t mSize;
+ std::vector<Block> mBlocks;
};
-inline BigBuffer::BigBuffer(size_t blockSize) : mBlockSize(blockSize), mSize(0) {
-}
+inline BigBuffer::BigBuffer(size_t blockSize)
+ : mBlockSize(blockSize), mSize(0) {}
-inline BigBuffer::BigBuffer(BigBuffer&& rhs) :
- mBlockSize(rhs.mBlockSize), mSize(rhs.mSize), mBlocks(std::move(rhs.mBlocks)) {
-}
+inline BigBuffer::BigBuffer(BigBuffer&& rhs)
+ : mBlockSize(rhs.mBlockSize),
+ mSize(rhs.mSize),
+ mBlocks(std::move(rhs.mBlocks)) {}
-inline size_t BigBuffer::size() const {
- return mSize;
-}
+inline size_t BigBuffer::size() const { return mSize; }
-inline size_t BigBuffer::getBlockSize() const {
- return mBlockSize;
-}
+inline size_t BigBuffer::getBlockSize() const { return mBlockSize; }
template <typename T>
inline T* BigBuffer::nextBlock(size_t count) {
- static_assert(std::is_standard_layout<T>::value, "T must be standard_layout type");
- assert(count != 0);
- return reinterpret_cast<T*>(nextBlockImpl(sizeof(T) * count));
+ static_assert(std::is_standard_layout<T>::value,
+ "T must be standard_layout type");
+ assert(count != 0);
+ return reinterpret_cast<T*>(nextBlockImpl(sizeof(T) * count));
}
inline void BigBuffer::backUp(size_t count) {
- Block& block = mBlocks.back();
- block.size -= count;
- mSize -= count;
+ Block& block = mBlocks.back();
+ block.size -= count;
+ mSize -= count;
}
inline void BigBuffer::appendBuffer(BigBuffer&& buffer) {
- std::move(buffer.mBlocks.begin(), buffer.mBlocks.end(), std::back_inserter(mBlocks));
- mSize += buffer.mSize;
- buffer.mBlocks.clear();
- buffer.mSize = 0;
+ std::move(buffer.mBlocks.begin(), buffer.mBlocks.end(),
+ std::back_inserter(mBlocks));
+ mSize += buffer.mSize;
+ buffer.mBlocks.clear();
+ buffer.mSize = 0;
}
-inline void BigBuffer::pad(size_t bytes) {
- nextBlock<char>(bytes);
-}
+inline void BigBuffer::pad(size_t bytes) { nextBlock<char>(bytes); }
inline void BigBuffer::align4() {
- const size_t unaligned = mSize % 4;
- if (unaligned != 0) {
- pad(4 - unaligned);
- }
+ const size_t unaligned = mSize % 4;
+ if (unaligned != 0) {
+ pad(4 - unaligned);
+ }
}
inline BigBuffer::const_iterator BigBuffer::begin() const {
- return mBlocks.begin();
+ return mBlocks.begin();
}
inline BigBuffer::const_iterator BigBuffer::end() const {
- return mBlocks.end();
+ return mBlocks.end();
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_BIG_BUFFER_H
+#endif // AAPT_BIG_BUFFER_H
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index c9b3811..4cba921 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -72,8 +72,8 @@
std::stringstream errorStr;
errorStr << "unable to open file: " << strerror(errno);
*outError = errorStr.str();
- return {};
}
+ return {};
}
std::vector<std::string> files;
diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h
index 52c2052..d90c6b6 100644
--- a/tools/aapt2/util/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -39,15 +39,15 @@
#endif
enum class FileType {
- kUnknown = 0,
- kNonexistant,
- kRegular,
- kDirectory,
- kCharDev,
- kBlockDev,
- kFifo,
- kSymlink,
- kSocket,
+ kUnknown = 0,
+ kNonexistant,
+ kRegular,
+ kDirectory,
+ kCharDev,
+ kBlockDev,
+ kFifo,
+ kSymlink,
+ kSocket,
};
FileType getFileType(const StringPiece& path);
@@ -93,12 +93,14 @@
/**
* Creates a FileMap for the file at path.
*/
-Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError);
+Maybe<android::FileMap> mmapPath(const StringPiece& path,
+ std::string* outError);
/**
* Reads the file at path and appends each line to the outArgList vector.
*/
-bool appendArgsFromFile(const StringPiece& path, std::vector<std::string>* outArgList,
+bool appendArgsFromFile(const StringPiece& path,
+ std::vector<std::string>* outArgList,
std::string* outError);
/*
@@ -108,37 +110,36 @@
* FileFilter::setPattern(const std::string&) method.
*/
class FileFilter {
-public:
- explicit FileFilter(IDiagnostics* diag) : mDiag(diag) {
- }
+ public:
+ explicit FileFilter(IDiagnostics* diag) : mDiag(diag) {}
- /*
- * Patterns syntax:
- * - Delimiter is :
- * - Entry can start with the flag ! to avoid printing a warning
- * about the file being ignored.
- * - Entry can have the flag "<dir>" to match only directories
- * or <file> to match only files. Default is to match both.
- * - Entry can be a simplified glob "<prefix>*" or "*<suffix>"
- * where prefix/suffix must have at least 1 character (so that
- * we don't match a '*' catch-all pattern.)
- * - The special filenames "." and ".." are always ignored.
- * - Otherwise the full string is matched.
- * - match is not case-sensitive.
- */
- bool setPattern(const StringPiece& pattern);
+ /*
+ * Patterns syntax:
+ * - Delimiter is :
+ * - Entry can start with the flag ! to avoid printing a warning
+ * about the file being ignored.
+ * - Entry can have the flag "<dir>" to match only directories
+ * or <file> to match only files. Default is to match both.
+ * - Entry can be a simplified glob "<prefix>*" or "*<suffix>"
+ * where prefix/suffix must have at least 1 character (so that
+ * we don't match a '*' catch-all pattern.)
+ * - The special filenames "." and ".." are always ignored.
+ * - Otherwise the full string is matched.
+ * - match is not case-sensitive.
+ */
+ bool setPattern(const StringPiece& pattern);
- /**
- * Applies the filter, returning true for pass, false for fail.
- */
- bool operator()(const std::string& filename, FileType type) const;
+ /**
+ * Applies the filter, returning true for pass, false for fail.
+ */
+ bool operator()(const std::string& filename, FileType type) const;
-private:
- IDiagnostics* mDiag;
- std::vector<std::string> mPatternTokens;
+ private:
+ IDiagnostics* mDiag;
+ std::vector<std::string> mPatternTokens;
};
-} // namespace file
-} // namespace aapt
+} // namespace file
+} // namespace aapt
-#endif // AAPT_FILES_H
+#endif // AAPT_FILES_H
diff --git a/tools/aapt2/util/ImmutableMap.h b/tools/aapt2/util/ImmutableMap.h
index b1f9e9d..6f48764 100644
--- a/tools/aapt2/util/ImmutableMap.h
+++ b/tools/aapt2/util/ImmutableMap.h
@@ -26,59 +26,57 @@
template <typename TKey, typename TValue>
class ImmutableMap {
- static_assert(is_comparable<TKey, TKey>::value, "key is not comparable");
+ static_assert(is_comparable<TKey, TKey>::value, "key is not comparable");
-private:
- std::vector<std::pair<TKey, TValue>> mData;
+ private:
+ std::vector<std::pair<TKey, TValue>> mData;
- explicit ImmutableMap(std::vector<std::pair<TKey, TValue>> data) : mData(std::move(data)) {
+ explicit ImmutableMap(std::vector<std::pair<TKey, TValue>> data)
+ : mData(std::move(data)) {}
+
+ public:
+ using const_iterator = typename decltype(mData)::const_iterator;
+
+ ImmutableMap(ImmutableMap&&) = default;
+ ImmutableMap& operator=(ImmutableMap&&) = default;
+
+ ImmutableMap(const ImmutableMap&) = delete;
+ ImmutableMap& operator=(const ImmutableMap&) = delete;
+
+ static ImmutableMap<TKey, TValue> createPreSorted(
+ std::initializer_list<std::pair<TKey, TValue>> list) {
+ return ImmutableMap(
+ std::vector<std::pair<TKey, TValue>>(list.begin(), list.end()));
+ }
+
+ static ImmutableMap<TKey, TValue> createAndSort(
+ std::initializer_list<std::pair<TKey, TValue>> list) {
+ std::vector<std::pair<TKey, TValue>> data(list.begin(), list.end());
+ std::sort(data.begin(), data.end());
+ return ImmutableMap(std::move(data));
+ }
+
+ template <typename TKey2, typename = typename std::enable_if<
+ is_comparable<TKey, TKey2>::value>::type>
+ const_iterator find(const TKey2& key) const {
+ auto cmp = [](const std::pair<TKey, TValue>& candidate,
+ const TKey2& target) -> bool {
+ return candidate.first < target;
+ };
+
+ const_iterator endIter = end();
+ auto iter = std::lower_bound(mData.begin(), endIter, key, cmp);
+ if (iter == endIter || iter->first == key) {
+ return iter;
}
+ return endIter;
+ }
-public:
- using const_iterator = typename decltype(mData)::const_iterator;
+ const_iterator begin() const { return mData.begin(); }
- ImmutableMap(ImmutableMap&&) = default;
- ImmutableMap& operator=(ImmutableMap&&) = default;
-
- ImmutableMap(const ImmutableMap&) = delete;
- ImmutableMap& operator=(const ImmutableMap&) = delete;
-
- static ImmutableMap<TKey, TValue> createPreSorted(
- std::initializer_list<std::pair<TKey, TValue>> list) {
- return ImmutableMap(std::vector<std::pair<TKey, TValue>>(list.begin(), list.end()));
- }
-
- static ImmutableMap<TKey, TValue> createAndSort(
- std::initializer_list<std::pair<TKey, TValue>> list) {
- std::vector<std::pair<TKey, TValue>> data(list.begin(), list.end());
- std::sort(data.begin(), data.end());
- return ImmutableMap(std::move(data));
- }
-
- template <typename TKey2,
- typename = typename std::enable_if<is_comparable<TKey, TKey2>::value>::type>
- const_iterator find(const TKey2& key) const {
- auto cmp = [](const std::pair<TKey, TValue>& candidate, const TKey2& target) -> bool {
- return candidate.first < target;
- };
-
- const_iterator endIter = end();
- auto iter = std::lower_bound(mData.begin(), endIter, key, cmp);
- if (iter == endIter || iter->first == key) {
- return iter;
- }
- return endIter;
- }
-
- const_iterator begin() const {
- return mData.begin();
- }
-
- const_iterator end() const {
- return mData.end();
- }
+ const_iterator end() const { return mData.end(); }
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_UTIL_IMMUTABLEMAP_H */
diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h
index 129f6d9..90a0198 100644
--- a/tools/aapt2/util/Maybe.h
+++ b/tools/aapt2/util/Maybe.h
@@ -32,303 +32,292 @@
*/
template <typename T>
class Maybe {
-public:
- /**
- * Construct Nothing.
- */
- Maybe();
+ public:
+ /**
+ * Construct Nothing.
+ */
+ Maybe();
- ~Maybe();
+ ~Maybe();
- Maybe(const Maybe& rhs);
+ Maybe(const Maybe& rhs);
- template <typename U>
- Maybe(const Maybe<U>& rhs); // NOLINT(implicit)
+ template <typename U>
+ Maybe(const Maybe<U>& rhs); // NOLINT(implicit)
- Maybe(Maybe&& rhs);
+ Maybe(Maybe&& rhs);
- template <typename U>
- Maybe(Maybe<U>&& rhs); // NOLINT(implicit)
+ template <typename U>
+ Maybe(Maybe<U>&& rhs); // NOLINT(implicit)
- Maybe& operator=(const Maybe& rhs);
+ Maybe& operator=(const Maybe& rhs);
- template <typename U>
- Maybe& operator=(const Maybe<U>& rhs);
+ template <typename U>
+ Maybe& operator=(const Maybe<U>& rhs);
- Maybe& operator=(Maybe&& rhs);
+ Maybe& operator=(Maybe&& rhs);
- template <typename U>
- Maybe& operator=(Maybe<U>&& rhs);
+ template <typename U>
+ Maybe& operator=(Maybe<U>&& rhs);
- /**
- * Construct a Maybe holding a value.
- */
- Maybe(const T& value); // NOLINT(implicit)
+ /**
+ * Construct a Maybe holding a value.
+ */
+ Maybe(const T& value); // NOLINT(implicit)
- /**
- * Construct a Maybe holding a value.
- */
- Maybe(T&& value); // NOLINT(implicit)
+ /**
+ * Construct a Maybe holding a value.
+ */
+ Maybe(T&& value); // NOLINT(implicit)
- /**
- * True if this holds a value, false if
- * it holds Nothing.
- */
- explicit operator bool() const;
+ /**
+ * True if this holds a value, false if
+ * it holds Nothing.
+ */
+ explicit operator bool() const;
- /**
- * Gets the value if one exists, or else
- * panics.
- */
- T& value();
+ /**
+ * Gets the value if one exists, or else
+ * panics.
+ */
+ T& value();
- /**
- * Gets the value if one exists, or else
- * panics.
- */
- const T& value() const;
+ /**
+ * Gets the value if one exists, or else
+ * panics.
+ */
+ const T& value() const;
- T valueOrDefault(const T& def) const;
+ T valueOrDefault(const T& def) const;
-private:
- template <typename U>
- friend class Maybe;
+ private:
+ template <typename U>
+ friend class Maybe;
- template <typename U>
- Maybe& copy(const Maybe<U>& rhs);
+ template <typename U>
+ Maybe& copy(const Maybe<U>& rhs);
- template <typename U>
- Maybe& move(Maybe<U>&& rhs);
+ template <typename U>
+ Maybe& move(Maybe<U>&& rhs);
- void destroy();
+ void destroy();
- bool mNothing;
+ bool mNothing;
- typename std::aligned_storage<sizeof(T), alignof(T)>::type mStorage;
+ typename std::aligned_storage<sizeof(T), alignof(T)>::type mStorage;
};
template <typename T>
-Maybe<T>::Maybe()
-: mNothing(true) {
-}
+Maybe<T>::Maybe() : mNothing(true) {}
template <typename T>
Maybe<T>::~Maybe() {
- if (!mNothing) {
- destroy();
- }
+ if (!mNothing) {
+ destroy();
+ }
}
template <typename T>
-Maybe<T>::Maybe(const Maybe& rhs)
-: mNothing(rhs.mNothing) {
- if (!rhs.mNothing) {
- new (&mStorage) T(reinterpret_cast<const T&>(rhs.mStorage));
- }
+Maybe<T>::Maybe(const Maybe& rhs) : mNothing(rhs.mNothing) {
+ if (!rhs.mNothing) {
+ new (&mStorage) T(reinterpret_cast<const T&>(rhs.mStorage));
+ }
}
template <typename T>
template <typename U>
-Maybe<T>::Maybe(const Maybe<U>& rhs)
-: mNothing(rhs.mNothing) {
- if (!rhs.mNothing) {
- new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage));
- }
+Maybe<T>::Maybe(const Maybe<U>& rhs) : mNothing(rhs.mNothing) {
+ if (!rhs.mNothing) {
+ new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage));
+ }
}
template <typename T>
-Maybe<T>::Maybe(Maybe&& rhs)
-: mNothing(rhs.mNothing) {
- if (!rhs.mNothing) {
- rhs.mNothing = true;
+Maybe<T>::Maybe(Maybe&& rhs) : mNothing(rhs.mNothing) {
+ if (!rhs.mNothing) {
+ rhs.mNothing = true;
- // Move the value from rhs.
- new (&mStorage) T(std::move(reinterpret_cast<T&>(rhs.mStorage)));
- rhs.destroy();
- }
+ // Move the value from rhs.
+ new (&mStorage) T(std::move(reinterpret_cast<T&>(rhs.mStorage)));
+ rhs.destroy();
+ }
}
template <typename T>
template <typename U>
-Maybe<T>::Maybe(Maybe<U>&& rhs)
-: mNothing(rhs.mNothing) {
- if (!rhs.mNothing) {
- rhs.mNothing = true;
+Maybe<T>::Maybe(Maybe<U>&& rhs) : mNothing(rhs.mNothing) {
+ if (!rhs.mNothing) {
+ rhs.mNothing = true;
- // Move the value from rhs.
- new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage)));
- rhs.destroy();
- }
+ // Move the value from rhs.
+ new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage)));
+ rhs.destroy();
+ }
}
template <typename T>
inline Maybe<T>& Maybe<T>::operator=(const Maybe& rhs) {
- // Delegate to the actual assignment.
- return copy(rhs);
+ // Delegate to the actual assignment.
+ return copy(rhs);
}
template <typename T>
template <typename U>
inline Maybe<T>& Maybe<T>::operator=(const Maybe<U>& rhs) {
- return copy(rhs);
+ return copy(rhs);
}
template <typename T>
template <typename U>
Maybe<T>& Maybe<T>::copy(const Maybe<U>& rhs) {
- if (mNothing && rhs.mNothing) {
- // Both are nothing, nothing to do.
- return *this;
- } else if (!mNothing && !rhs.mNothing) {
- // We both are something, so assign rhs to us.
- reinterpret_cast<T&>(mStorage) = reinterpret_cast<const U&>(rhs.mStorage);
- } else if (mNothing) {
- // We are nothing but rhs is something.
- mNothing = rhs.mNothing;
-
- // Copy the value from rhs.
- new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage));
- } else {
- // We are something but rhs is nothing, so destroy our value.
- mNothing = rhs.mNothing;
- destroy();
- }
+ if (mNothing && rhs.mNothing) {
+ // Both are nothing, nothing to do.
return *this;
+ } else if (!mNothing && !rhs.mNothing) {
+ // We both are something, so assign rhs to us.
+ reinterpret_cast<T&>(mStorage) = reinterpret_cast<const U&>(rhs.mStorage);
+ } else if (mNothing) {
+ // We are nothing but rhs is something.
+ mNothing = rhs.mNothing;
+
+ // Copy the value from rhs.
+ new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage));
+ } else {
+ // We are something but rhs is nothing, so destroy our value.
+ mNothing = rhs.mNothing;
+ destroy();
+ }
+ return *this;
}
template <typename T>
inline Maybe<T>& Maybe<T>::operator=(Maybe&& rhs) {
- // Delegate to the actual assignment.
- return move(std::forward<Maybe<T>>(rhs));
+ // Delegate to the actual assignment.
+ return move(std::forward<Maybe<T>>(rhs));
}
template <typename T>
template <typename U>
inline Maybe<T>& Maybe<T>::operator=(Maybe<U>&& rhs) {
- return move(std::forward<Maybe<U>>(rhs));
+ return move(std::forward<Maybe<U>>(rhs));
}
template <typename T>
template <typename U>
Maybe<T>& Maybe<T>::move(Maybe<U>&& rhs) {
- if (mNothing && rhs.mNothing) {
- // Both are nothing, nothing to do.
- return *this;
- } else if (!mNothing && !rhs.mNothing) {
- // We both are something, so move assign rhs to us.
- rhs.mNothing = true;
- reinterpret_cast<T&>(mStorage) = std::move(reinterpret_cast<U&>(rhs.mStorage));
- rhs.destroy();
- } else if (mNothing) {
- // We are nothing but rhs is something.
- mNothing = false;
- rhs.mNothing = true;
-
- // Move the value from rhs.
- new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage)));
- rhs.destroy();
- } else {
- // We are something but rhs is nothing, so destroy our value.
- mNothing = true;
- destroy();
- }
+ if (mNothing && rhs.mNothing) {
+ // Both are nothing, nothing to do.
return *this;
+ } else if (!mNothing && !rhs.mNothing) {
+ // We both are something, so move assign rhs to us.
+ rhs.mNothing = true;
+ reinterpret_cast<T&>(mStorage) =
+ std::move(reinterpret_cast<U&>(rhs.mStorage));
+ rhs.destroy();
+ } else if (mNothing) {
+ // We are nothing but rhs is something.
+ mNothing = false;
+ rhs.mNothing = true;
+
+ // Move the value from rhs.
+ new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage)));
+ rhs.destroy();
+ } else {
+ // We are something but rhs is nothing, so destroy our value.
+ mNothing = true;
+ destroy();
+ }
+ return *this;
}
template <typename T>
-Maybe<T>::Maybe(const T& value)
-: mNothing(false) {
- new (&mStorage) T(value);
+Maybe<T>::Maybe(const T& value) : mNothing(false) {
+ new (&mStorage) T(value);
}
template <typename T>
-Maybe<T>::Maybe(T&& value)
-: mNothing(false) {
- new (&mStorage) T(std::forward<T>(value));
+Maybe<T>::Maybe(T&& value) : mNothing(false) {
+ new (&mStorage) T(std::forward<T>(value));
}
template <typename T>
Maybe<T>::operator bool() const {
- return !mNothing;
+ return !mNothing;
}
template <typename T>
T& Maybe<T>::value() {
- assert(!mNothing && "Maybe<T>::value() called on Nothing");
- return reinterpret_cast<T&>(mStorage);
+ assert(!mNothing && "Maybe<T>::value() called on Nothing");
+ return reinterpret_cast<T&>(mStorage);
}
template <typename T>
const T& Maybe<T>::value() const {
- assert(!mNothing && "Maybe<T>::value() called on Nothing");
- return reinterpret_cast<const T&>(mStorage);
+ assert(!mNothing && "Maybe<T>::value() called on Nothing");
+ return reinterpret_cast<const T&>(mStorage);
}
template <typename T>
T Maybe<T>::valueOrDefault(const T& def) const {
- if (mNothing) {
- return def;
- }
- return reinterpret_cast<const T&>(mStorage);
+ if (mNothing) {
+ return def;
+ }
+ return reinterpret_cast<const T&>(mStorage);
}
template <typename T>
void Maybe<T>::destroy() {
- reinterpret_cast<T&>(mStorage).~T();
+ reinterpret_cast<T&>(mStorage).~T();
}
template <typename T>
inline Maybe<typename std::remove_reference<T>::type> make_value(T&& value) {
- return Maybe<typename std::remove_reference<T>::type>(std::forward<T>(value));
+ return Maybe<typename std::remove_reference<T>::type>(std::forward<T>(value));
}
template <typename T>
inline Maybe<T> make_nothing() {
- return Maybe<T>();
+ return Maybe<T>();
}
/**
- * Define the == operator between Maybe<T> and Maybe<U> only if the operator T == U is defined.
- * That way the compiler will show an error at the callsite when comparing two Maybe<> objects
+ * Define the == operator between Maybe<T> and Maybe<U> only if the operator T
+ * == U is defined.
+ * That way the compiler will show an error at the callsite when comparing two
+ * Maybe<> objects
* whose inner types can't be compared.
*/
template <typename T, typename U>
-typename std::enable_if<
- has_eq_op<T, U>::value,
- bool
->::type operator==(const Maybe<T>& a, const Maybe<U>& b) {
- if (a && b) {
- return a.value() == b.value();
- } else if (!a && !b) {
- return true;
- }
- return false;
+typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==(
+ const Maybe<T>& a, const Maybe<U>& b) {
+ if (a && b) {
+ return a.value() == b.value();
+ } else if (!a && !b) {
+ return true;
+ }
+ return false;
}
/**
* Same as operator== but negated.
*/
template <typename T, typename U>
-typename std::enable_if<
- has_eq_op<T, U>::value,
- bool
->::type operator!=(const Maybe<T>& a, const Maybe<U>& b) {
- return !(a == b);
+typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator!=(
+ const Maybe<T>& a, const Maybe<U>& b) {
+ return !(a == b);
}
template <typename T, typename U>
-typename std::enable_if<
- has_lt_op<T, U>::value,
- bool
->::type operator<(const Maybe<T>& a, const Maybe<U>& b) {
- if (a && b) {
- return a.value() < b.value();
- } else if (!a && !b) {
- return false;
- }
- return !a;
+typename std::enable_if<has_lt_op<T, U>::value, bool>::type operator<(
+ const Maybe<T>& a, const Maybe<U>& b) {
+ if (a && b) {
+ return a.value() < b.value();
+ } else if (!a && !b) {
+ return false;
+ }
+ return !a;
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_MAYBE_H
+#endif // AAPT_MAYBE_H
diff --git a/tools/aapt2/util/StringPiece.h b/tools/aapt2/util/StringPiece.h
index 266c003..de93822 100644
--- a/tools/aapt2/util/StringPiece.h
+++ b/tools/aapt2/util/StringPiece.h
@@ -17,11 +17,11 @@
#ifndef AAPT_STRING_PIECE_H
#define AAPT_STRING_PIECE_H
-#include <ostream>
-#include <string>
#include <utils/JenkinsHash.h>
#include <utils/String8.h>
#include <utils/Unicode.h>
+#include <ostream>
+#include <string>
namespace aapt {
@@ -35,45 +35,46 @@
*/
template <typename TChar>
class BasicStringPiece {
-public:
- using const_iterator = const TChar*;
- using difference_type = size_t;
+ public:
+ using const_iterator = const TChar*;
+ using difference_type = size_t;
- // End of string marker.
- constexpr static const size_t npos = static_cast<size_t>(-1);
+ // End of string marker.
+ constexpr static const size_t npos = static_cast<size_t>(-1);
- BasicStringPiece();
- BasicStringPiece(const BasicStringPiece<TChar>& str);
- BasicStringPiece(const std::basic_string<TChar>& str); // NOLINT(implicit)
- BasicStringPiece(const TChar* str); // NOLINT(implicit)
- BasicStringPiece(const TChar* str, size_t len);
+ BasicStringPiece();
+ BasicStringPiece(const BasicStringPiece<TChar>& str);
+ BasicStringPiece(const std::basic_string<TChar>& str); // NOLINT(implicit)
+ BasicStringPiece(const TChar* str); // NOLINT(implicit)
+ BasicStringPiece(const TChar* str, size_t len);
- BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs);
- BasicStringPiece<TChar>& assign(const TChar* str, size_t len);
+ BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs);
+ BasicStringPiece<TChar>& assign(const TChar* str, size_t len);
- BasicStringPiece<TChar> substr(size_t start, size_t len = npos) const;
- BasicStringPiece<TChar> substr(BasicStringPiece<TChar>::const_iterator begin,
- BasicStringPiece<TChar>::const_iterator end) const;
+ BasicStringPiece<TChar> substr(size_t start, size_t len = npos) const;
+ BasicStringPiece<TChar> substr(
+ BasicStringPiece<TChar>::const_iterator begin,
+ BasicStringPiece<TChar>::const_iterator end) const;
- const TChar* data() const;
- size_t length() const;
- size_t size() const;
- bool empty() const;
- std::basic_string<TChar> toString() const;
+ const TChar* data() const;
+ size_t length() const;
+ size_t size() const;
+ bool empty() const;
+ std::basic_string<TChar> toString() const;
- bool contains(const BasicStringPiece<TChar>& rhs) const;
- int compare(const BasicStringPiece<TChar>& rhs) const;
- bool operator<(const BasicStringPiece<TChar>& rhs) const;
- bool operator>(const BasicStringPiece<TChar>& rhs) const;
- bool operator==(const BasicStringPiece<TChar>& rhs) const;
- bool operator!=(const BasicStringPiece<TChar>& rhs) const;
+ bool contains(const BasicStringPiece<TChar>& rhs) const;
+ int compare(const BasicStringPiece<TChar>& rhs) const;
+ bool operator<(const BasicStringPiece<TChar>& rhs) const;
+ bool operator>(const BasicStringPiece<TChar>& rhs) const;
+ bool operator==(const BasicStringPiece<TChar>& rhs) const;
+ bool operator!=(const BasicStringPiece<TChar>& rhs) const;
- const_iterator begin() const;
- const_iterator end() const;
+ const_iterator begin() const;
+ const_iterator end() const;
-private:
- const TChar* mData;
- size_t mLength;
+ private:
+ const TChar* mData;
+ size_t mLength;
};
using StringPiece = BasicStringPiece<char>;
@@ -87,198 +88,210 @@
constexpr const size_t BasicStringPiece<TChar>::npos;
template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece() : mData(nullptr) , mLength(0) {
-}
+inline BasicStringPiece<TChar>::BasicStringPiece()
+ : mData(nullptr), mLength(0) {}
template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str) :
- mData(str.mData), mLength(str.mLength) {
-}
+inline BasicStringPiece<TChar>::BasicStringPiece(
+ const BasicStringPiece<TChar>& str)
+ : mData(str.mData), mLength(str.mLength) {}
template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str) :
- mData(str.data()), mLength(str.length()) {
-}
+inline BasicStringPiece<TChar>::BasicStringPiece(
+ const std::basic_string<TChar>& str)
+ : mData(str.data()), mLength(str.length()) {}
template <>
-inline BasicStringPiece<char>::BasicStringPiece(const char* str) :
- mData(str), mLength(str != nullptr ? strlen(str) : 0) {
-}
+inline BasicStringPiece<char>::BasicStringPiece(const char* str)
+ : mData(str), mLength(str != nullptr ? strlen(str) : 0) {}
template <>
-inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str) :
- mData(str), mLength(str != nullptr ? strlen16(str) : 0) {
-}
+inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str)
+ : mData(str), mLength(str != nullptr ? strlen16(str) : 0) {}
template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len) :
- mData(str), mLength(len) {
-}
+inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len)
+ : mData(str), mLength(len) {}
template <typename TChar>
inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::operator=(
- const BasicStringPiece<TChar>& rhs) {
- mData = rhs.mData;
- mLength = rhs.mLength;
- return *this;
+ const BasicStringPiece<TChar>& rhs) {
+ mData = rhs.mData;
+ mLength = rhs.mLength;
+ return *this;
}
template <typename TChar>
-inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(const TChar* str, size_t len) {
- mData = str;
- mLength = len;
- return *this;
-}
-
-
-template <typename TChar>
-inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(size_t start, size_t len) const {
- if (len == npos) {
- len = mLength - start;
- }
-
- if (start > mLength || start + len > mLength) {
- return BasicStringPiece<TChar>();
- }
- return BasicStringPiece<TChar>(mData + start, len);
+inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(
+ const TChar* str, size_t len) {
+ mData = str;
+ mLength = len;
+ return *this;
}
template <typename TChar>
inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(
- BasicStringPiece<TChar>::const_iterator begin,
- BasicStringPiece<TChar>::const_iterator end) const {
- return BasicStringPiece<TChar>(begin, end - begin);
+ size_t start, size_t len) const {
+ if (len == npos) {
+ len = mLength - start;
+ }
+
+ if (start > mLength || start + len > mLength) {
+ return BasicStringPiece<TChar>();
+ }
+ return BasicStringPiece<TChar>(mData + start, len);
+}
+
+template <typename TChar>
+inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(
+ BasicStringPiece<TChar>::const_iterator begin,
+ BasicStringPiece<TChar>::const_iterator end) const {
+ return BasicStringPiece<TChar>(begin, end - begin);
}
template <typename TChar>
inline const TChar* BasicStringPiece<TChar>::data() const {
- return mData;
+ return mData;
}
template <typename TChar>
inline size_t BasicStringPiece<TChar>::length() const {
- return mLength;
+ return mLength;
}
template <typename TChar>
inline size_t BasicStringPiece<TChar>::size() const {
- return mLength;
+ return mLength;
}
template <typename TChar>
inline bool BasicStringPiece<TChar>::empty() const {
- return mLength == 0;
+ return mLength == 0;
}
template <typename TChar>
inline std::basic_string<TChar> BasicStringPiece<TChar>::toString() const {
- return std::basic_string<TChar>(mData, mLength);
+ return std::basic_string<TChar>(mData, mLength);
}
template <>
-inline bool BasicStringPiece<char>::contains(const BasicStringPiece<char>& rhs) const {
- if (!mData || !rhs.mData) {
- return false;
- }
- if (rhs.mLength > mLength) {
- return false;
- }
- return strstr(mData, rhs.mData) != nullptr;
+inline bool BasicStringPiece<char>::contains(
+ const BasicStringPiece<char>& rhs) const {
+ if (!mData || !rhs.mData) {
+ return false;
+ }
+ if (rhs.mLength > mLength) {
+ return false;
+ }
+ return strstr(mData, rhs.mData) != nullptr;
}
template <>
-inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const {
- const char nullStr = '\0';
- const char* b1 = mData != nullptr ? mData : &nullStr;
- const char* e1 = b1 + mLength;
- const char* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr;
- const char* e2 = b2 + rhs.mLength;
+inline int BasicStringPiece<char>::compare(
+ const BasicStringPiece<char>& rhs) const {
+ const char nullStr = '\0';
+ const char* b1 = mData != nullptr ? mData : &nullStr;
+ const char* e1 = b1 + mLength;
+ const char* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr;
+ const char* e2 = b2 + rhs.mLength;
- while (b1 < e1 && b2 < e2) {
- const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++);
- if (d) {
- return d;
- }
+ while (b1 < e1 && b2 < e2) {
+ const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++);
+ if (d) {
+ return d;
}
- return static_cast<int>(mLength - rhs.mLength);
+ }
+ return static_cast<int>(mLength - rhs.mLength);
}
-inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char16_t>& str) {
- android::String8 utf8(str.data(), str.size());
- return out.write(utf8.string(), utf8.size());
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const BasicStringPiece<char16_t>& str) {
+ android::String8 utf8(str.data(), str.size());
+ return out.write(utf8.string(), utf8.size());
}
template <>
-inline bool BasicStringPiece<char16_t>::contains(const BasicStringPiece<char16_t>& rhs) const {
- if (!mData || !rhs.mData) {
- return false;
- }
- if (rhs.mLength > mLength) {
- return false;
- }
- return strstr16(mData, rhs.mData) != nullptr;
+inline bool BasicStringPiece<char16_t>::contains(
+ const BasicStringPiece<char16_t>& rhs) const {
+ if (!mData || !rhs.mData) {
+ return false;
+ }
+ if (rhs.mLength > mLength) {
+ return false;
+ }
+ return strstr16(mData, rhs.mData) != nullptr;
}
template <>
-inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const {
- const char16_t nullStr = u'\0';
- const char16_t* b1 = mData != nullptr ? mData : &nullStr;
- const char16_t* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr;
- return strzcmp16(b1, mLength, b2, rhs.mLength);
+inline int BasicStringPiece<char16_t>::compare(
+ const BasicStringPiece<char16_t>& rhs) const {
+ const char16_t nullStr = u'\0';
+ const char16_t* b1 = mData != nullptr ? mData : &nullStr;
+ const char16_t* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr;
+ return strzcmp16(b1, mLength, b2, rhs.mLength);
}
template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator<(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) < 0;
+inline bool BasicStringPiece<TChar>::operator<(
+ const BasicStringPiece<TChar>& rhs) const {
+ return compare(rhs) < 0;
}
template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator>(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) > 0;
+inline bool BasicStringPiece<TChar>::operator>(
+ const BasicStringPiece<TChar>& rhs) const {
+ return compare(rhs) > 0;
}
template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator==(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) == 0;
+inline bool BasicStringPiece<TChar>::operator==(
+ const BasicStringPiece<TChar>& rhs) const {
+ return compare(rhs) == 0;
}
template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator!=(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) != 0;
+inline bool BasicStringPiece<TChar>::operator!=(
+ const BasicStringPiece<TChar>& rhs) const {
+ return compare(rhs) != 0;
}
template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::begin() const {
- return mData;
+inline typename BasicStringPiece<TChar>::const_iterator
+BasicStringPiece<TChar>::begin() const {
+ return mData;
}
template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::end() const {
- return mData + mLength;
+inline typename BasicStringPiece<TChar>::const_iterator
+BasicStringPiece<TChar>::end() const {
+ return mData + mLength;
}
-inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) {
- return out.write(str.data(), str.size());
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const BasicStringPiece<char>& str) {
+ return out.write(str.data(), str.size());
}
-} // namespace aapt
+} // namespace aapt
-inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) {
- android::String8 utf8(str.data(), str.size());
- return out.write(utf8.string(), utf8.size());
+inline ::std::ostream& operator<<(::std::ostream& out,
+ const std::u16string& str) {
+ android::String8 utf8(str.data(), str.size());
+ return out.write(utf8.string(), utf8.size());
}
namespace std {
template <typename TChar>
struct hash<aapt::BasicStringPiece<TChar>> {
- size_t operator()(const aapt::BasicStringPiece<TChar>& str) const {
- uint32_t hashCode = android::JenkinsHashMixBytes(
- 0, reinterpret_cast<const uint8_t*>(str.data()), sizeof(TChar) * str.size());
- return static_cast<size_t>(hashCode);
- }
+ size_t operator()(const aapt::BasicStringPiece<TChar>& str) const {
+ uint32_t hashCode = android::JenkinsHashMixBytes(
+ 0, reinterpret_cast<const uint8_t*>(str.data()),
+ sizeof(TChar) * str.size());
+ return static_cast<size_t>(hashCode);
+ }
};
-} // namespace std
+} // namespace std
-#endif // AAPT_STRING_PIECE_H
+#endif // AAPT_STRING_PIECE_H
diff --git a/tools/aapt2/util/TypeTraits.h b/tools/aapt2/util/TypeTraits.h
index 76c13d6..b6539ed 100644
--- a/tools/aapt2/util/TypeTraits.h
+++ b/tools/aapt2/util/TypeTraits.h
@@ -21,19 +21,20 @@
namespace aapt {
-#define DEFINE_HAS_BINARY_OP_TRAIT(name, op) \
- template <typename T, typename U> \
- struct name { \
- template <typename V, typename W> \
- static constexpr decltype(std::declval<V>() op std::declval<W>(), bool()) test(int) { \
- return true; \
- } \
- template <typename V, typename W> \
- static constexpr bool test(...) { \
- return false; \
- } \
- static constexpr bool value = test<T, U>(int()); \
-}
+#define DEFINE_HAS_BINARY_OP_TRAIT(name, op) \
+ template <typename T, typename U> \
+ struct name { \
+ template <typename V, typename W> \
+ static constexpr decltype(std::declval<V>() op std::declval<W>(), bool()) \
+ test(int) { \
+ return true; \
+ } \
+ template <typename V, typename W> \
+ static constexpr bool test(...) { \
+ return false; \
+ } \
+ static constexpr bool value = test<T, U>(int()); \
+ }
DEFINE_HAS_BINARY_OP_TRAIT(has_eq_op, ==);
DEFINE_HAS_BINARY_OP_TRAIT(has_lt_op, <);
@@ -43,9 +44,10 @@
*/
template <typename T, typename U>
struct is_comparable {
- static constexpr bool value = has_eq_op<T, U>::value && has_lt_op<T, U>::value;
+ static constexpr bool value =
+ has_eq_op<T, U>::value && has_lt_op<T, U>::value;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_UTIL_TYPETRAITS_H */
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 9c88354..077e193 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -56,16 +56,14 @@
* UTF-16 isspace(). It basically checks for lower range characters that are
* whitespace.
*/
-inline bool isspace16(char16_t c) {
- return c < 0x0080 && isspace(c);
-}
+inline bool isspace16(char16_t c) { return c < 0x0080 && isspace(c); }
/**
* Returns an iterator to the first character that is not alpha-numeric and that
* is not in the allowedChars set.
*/
-StringPiece::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece& str,
- const StringPiece& allowedChars);
+StringPiece::const_iterator findNonAlphaNumericAndNotInSet(
+ const StringPiece& str, const StringPiece& allowedChars);
/**
* Tests that the string is a valid Java class name.
@@ -78,7 +76,8 @@
bool isJavaPackageName(const StringPiece& str);
/**
- * Converts the class name to a fully qualified class name from the given `package`. Ex:
+ * Converts the class name to a fully qualified class name from the given
+ * `package`. Ex:
*
* asdf --> package.asdf
* .asdf --> package.asdf
@@ -89,111 +88,114 @@
const StringPiece& className);
/**
- * Makes a std::unique_ptr<> with the template parameter inferred by the compiler.
+ * Makes a std::unique_ptr<> with the template parameter inferred by the
+ * compiler.
* This will be present in C++14 and can be removed then.
*/
template <typename T, class... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
- return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
+ return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
}
/**
- * Writes a set of items to the std::ostream, joining the times with the provided
+ * Writes a set of items to the std::ostream, joining the times with the
+ * provided
* separator.
*/
template <typename Container>
-::std::function<::std::ostream&(::std::ostream&)> joiner(const Container& container,
- const char* sep) {
- using std::begin;
- using std::end;
- const auto beginIter = begin(container);
- const auto endIter = end(container);
- return [beginIter, endIter, sep](::std::ostream& out) -> ::std::ostream& {
- for (auto iter = beginIter; iter != endIter; ++iter) {
- if (iter != beginIter) {
- out << sep;
- }
- out << *iter;
- }
- return out;
- };
+::std::function<::std::ostream&(::std::ostream&)> joiner(
+ const Container& container, const char* sep) {
+ using std::begin;
+ using std::end;
+ const auto beginIter = begin(container);
+ const auto endIter = end(container);
+ return [beginIter, endIter, sep](::std::ostream& out) -> ::std::ostream& {
+ for (auto iter = beginIter; iter != endIter; ++iter) {
+ if (iter != beginIter) {
+ out << sep;
+ }
+ out << *iter;
+ }
+ return out;
+ };
}
-inline ::std::function<::std::ostream&(::std::ostream&)> formatSize(size_t size) {
- return [size](::std::ostream& out) -> ::std::ostream& {
- constexpr size_t K = 1024u;
- constexpr size_t M = K * K;
- constexpr size_t G = M * K;
- if (size < K) {
- out << size << "B";
- } else if (size < M) {
- out << (double(size) / K) << " KiB";
- } else if (size < G) {
- out << (double(size) / M) << " MiB";
- } else {
- out << (double(size) / G) << " GiB";
- }
- return out;
- };
+inline ::std::function<::std::ostream&(::std::ostream&)> formatSize(
+ size_t size) {
+ return [size](::std::ostream& out) -> ::std::ostream& {
+ constexpr size_t K = 1024u;
+ constexpr size_t M = K * K;
+ constexpr size_t G = M * K;
+ if (size < K) {
+ out << size << "B";
+ } else if (size < M) {
+ out << (double(size) / K) << " KiB";
+ } else if (size < G) {
+ out << (double(size) / M) << " MiB";
+ } else {
+ out << (double(size) / G) << " GiB";
+ }
+ return out;
+ };
}
/**
- * Helper method to extract a UTF-16 string from a StringPool. If the string is stored as UTF-8,
+ * Helper method to extract a UTF-16 string from a StringPool. If the string is
+ * stored as UTF-8,
* the conversion to UTF-16 happens within ResStringPool.
*/
StringPiece16 getString16(const android::ResStringPool& pool, size_t idx);
/**
- * Helper method to extract a UTF-8 string from a StringPool. If the string is stored as UTF-16,
- * the conversion from UTF-16 to UTF-8 does not happen in ResStringPool and is done by this method,
- * which maintains no state or cache. This means we must return an std::string copy.
+ * Helper method to extract a UTF-8 string from a StringPool. If the string is
+ * stored as UTF-16,
+ * the conversion from UTF-16 to UTF-8 does not happen in ResStringPool and is
+ * done by this method,
+ * which maintains no state or cache. This means we must return an std::string
+ * copy.
*/
std::string getString(const android::ResStringPool& pool, size_t idx);
/**
- * Checks that the Java string format contains no non-positional arguments (arguments without
- * explicitly specifying an index) when there are more than one argument. This is an error
- * because translations may rearrange the order of the arguments in the string, which will
+ * Checks that the Java string format contains no non-positional arguments
+ * (arguments without
+ * explicitly specifying an index) when there are more than one argument. This
+ * is an error
+ * because translations may rearrange the order of the arguments in the string,
+ * which will
* break the string interpolation.
*/
bool verifyJavaStringFormat(const StringPiece& str);
class StringBuilder {
-public:
- StringBuilder& append(const StringPiece& str);
- const std::string& str() const;
- const std::string& error() const;
+ public:
+ StringBuilder& append(const StringPiece& str);
+ const std::string& str() const;
+ const std::string& error() const;
- // When building StyledStrings, we need UTF-16 indices into the string,
- // which is what the Java layer expects when dealing with java String.charAt().
- size_t utf16Len() const;
+ // When building StyledStrings, we need UTF-16 indices into the string,
+ // which is what the Java layer expects when dealing with java
+ // String.charAt().
+ size_t utf16Len() const;
- operator bool() const;
+ operator bool() const;
-private:
- std::string mStr;
- size_t mUtf16Len = 0;
- bool mQuote = false;
- bool mTrailingSpace = false;
- bool mLastCharWasEscape = false;
- std::string mError;
+ private:
+ std::string mStr;
+ size_t mUtf16Len = 0;
+ bool mQuote = false;
+ bool mTrailingSpace = false;
+ bool mLastCharWasEscape = false;
+ std::string mError;
};
-inline const std::string& StringBuilder::str() const {
- return mStr;
-}
+inline const std::string& StringBuilder::str() const { return mStr; }
-inline const std::string& StringBuilder::error() const {
- return mError;
-}
+inline const std::string& StringBuilder::error() const { return mError; }
-inline size_t StringBuilder::utf16Len() const {
- return mUtf16Len;
-}
+inline size_t StringBuilder::utf16Len() const { return mUtf16Len; }
-inline StringBuilder::operator bool() const {
- return mError.empty();
-}
+inline StringBuilder::operator bool() const { return mError.empty(); }
/**
* Converts a UTF8 string to a UTF16 string.
@@ -216,65 +218,51 @@
* any memory on the heap nor use standard containers.
*/
class Tokenizer {
-public:
- class iterator {
- public:
- iterator(const iterator&) = default;
- iterator& operator=(const iterator&) = default;
+ public:
+ class iterator {
+ public:
+ iterator(const iterator&) = default;
+ iterator& operator=(const iterator&) = default;
- iterator& operator++();
+ iterator& operator++();
- StringPiece operator*() {
- return mToken;
- }
- bool operator==(const iterator& rhs) const;
- bool operator!=(const iterator& rhs) const;
+ StringPiece operator*() { return mToken; }
+ bool operator==(const iterator& rhs) const;
+ bool operator!=(const iterator& rhs) const;
- private:
- friend class Tokenizer;
+ private:
+ friend class Tokenizer;
- iterator(StringPiece s, char sep, StringPiece tok, bool end);
+ iterator(StringPiece s, char sep, StringPiece tok, bool end);
- StringPiece mStr;
- char mSeparator;
- StringPiece mToken;
- bool mEnd;
- };
+ StringPiece mStr;
+ char mSeparator;
+ StringPiece mToken;
+ bool mEnd;
+ };
- Tokenizer(StringPiece str, char sep);
+ Tokenizer(StringPiece str, char sep);
- iterator begin() {
- return mBegin;
- }
+ iterator begin() { return mBegin; }
- iterator end() {
- return mEnd;
- }
+ iterator end() { return mEnd; }
-private:
- const iterator mBegin;
- const iterator mEnd;
+ private:
+ const iterator mBegin;
+ const iterator mEnd;
};
inline Tokenizer tokenize(const StringPiece& str, char sep) {
- return Tokenizer(str, sep);
+ return Tokenizer(str, sep);
}
-inline uint16_t hostToDevice16(uint16_t value) {
- return htods(value);
-}
+inline uint16_t hostToDevice16(uint16_t value) { return htods(value); }
-inline uint32_t hostToDevice32(uint32_t value) {
- return htodl(value);
-}
+inline uint32_t hostToDevice32(uint32_t value) { return htodl(value); }
-inline uint16_t deviceToHost16(uint16_t value) {
- return dtohs(value);
-}
+inline uint16_t deviceToHost16(uint16_t value) { return dtohs(value); }
-inline uint32_t deviceToHost32(uint32_t value) {
- return dtohl(value);
-}
+inline uint32_t deviceToHost32(uint32_t value) { return dtohl(value); }
/**
* Given a path like: res/xml-sw600dp/foo.xml
@@ -288,17 +276,19 @@
bool extractResFilePathParts(const StringPiece& path, StringPiece* outPrefix,
StringPiece* outEntry, StringPiece* outSuffix);
-} // namespace util
+} // namespace util
/**
- * Stream operator for functions. Calls the function with the stream as an argument.
+ * Stream operator for functions. Calls the function with the stream as an
+ * argument.
* In the aapt namespace for lookup.
*/
-inline ::std::ostream& operator<<(::std::ostream& out,
- const ::std::function<::std::ostream&(::std::ostream&)>& f) {
- return f(out);
+inline ::std::ostream& operator<<(
+ ::std::ostream& out,
+ const ::std::function<::std::ostream&(::std::ostream&)>& f) {
+ return f(out);
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_UTIL_H
+#endif // AAPT_UTIL_H
diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h
index cad508c..ca21b08 100644
--- a/tools/aapt2/xml/XmlActionExecutor.h
+++ b/tools/aapt2/xml/XmlActionExecutor.h
@@ -30,79 +30,84 @@
namespace xml {
enum class XmlActionExecutorPolicy {
- /**
- * Actions on run if elements are matched, errors occur only when actions return false.
- */
- None,
+ /**
+ * Actions on run if elements are matched, errors occur only when actions
+ * return false.
+ */
+ None,
- /**
- * The actions defined must match and run. If an element is found that does not match
- * an action, an error occurs.
- */
- Whitelist,
+ /**
+ * The actions defined must match and run. If an element is found that does
+ * not match
+ * an action, an error occurs.
+ */
+ Whitelist,
};
/**
- * Contains the actions to perform at this XML node. This is a recursive data structure that
+ * Contains the actions to perform at this XML node. This is a recursive data
+ * structure that
* holds XmlNodeActions for child XML nodes.
*/
class XmlNodeAction {
-public:
- using ActionFuncWithDiag = std::function<bool(Element*, SourcePathDiagnostics*)>;
- using ActionFunc = std::function<bool(Element*)>;
+ public:
+ using ActionFuncWithDiag =
+ std::function<bool(Element*, SourcePathDiagnostics*)>;
+ using ActionFunc = std::function<bool(Element*)>;
- /**
- * Find or create a child XmlNodeAction that will be performed for the child element
- * with the name `name`.
- */
- XmlNodeAction& operator[](const std::string& name) {
- return mMap[name];
- }
+ /**
+ * Find or create a child XmlNodeAction that will be performed for the child
+ * element
+ * with the name `name`.
+ */
+ XmlNodeAction& operator[](const std::string& name) { return mMap[name]; }
- /**
- * Add an action to be performed at this XmlNodeAction.
- */
- void action(ActionFunc f);
- void action(ActionFuncWithDiag);
+ /**
+ * Add an action to be performed at this XmlNodeAction.
+ */
+ void action(ActionFunc f);
+ void action(ActionFuncWithDiag);
-private:
- friend class XmlActionExecutor;
+ private:
+ friend class XmlActionExecutor;
- bool execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag, Element* el) const;
+ bool execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag,
+ Element* el) const;
- std::map<std::string, XmlNodeAction> mMap;
- std::vector<ActionFuncWithDiag> mActions;
+ std::map<std::string, XmlNodeAction> mMap;
+ std::vector<ActionFuncWithDiag> mActions;
};
/**
- * Allows the definition of actions to execute at specific XML elements defined by their
+ * Allows the definition of actions to execute at specific XML elements defined
+ * by their
* hierarchy.
*/
class XmlActionExecutor {
-public:
- XmlActionExecutor() = default;
+ public:
+ XmlActionExecutor() = default;
- /**
- * Find or create a root XmlNodeAction that will be performed for the root XML element
- * with the name `name`.
- */
- XmlNodeAction& operator[](const std::string& name) {
- return mMap[name];
- }
+ /**
+ * Find or create a root XmlNodeAction that will be performed for the root XML
+ * element
+ * with the name `name`.
+ */
+ XmlNodeAction& operator[](const std::string& name) { return mMap[name]; }
- /**
- * Execute the defined actions for this XmlResource.
- * Returns true if all actions return true, otherwise returns false.
- */
- bool execute(XmlActionExecutorPolicy policy, IDiagnostics* diag, XmlResource* doc) const;
+ /**
+ * Execute the defined actions for this XmlResource.
+ * Returns true if all actions return true, otherwise returns false.
+ */
+ bool execute(XmlActionExecutorPolicy policy, IDiagnostics* diag,
+ XmlResource* doc) const;
-private:
- std::map<std::string, XmlNodeAction> mMap;
+ private:
+ std::map<std::string, XmlNodeAction> mMap;
- DISALLOW_COPY_AND_ASSIGN(XmlActionExecutor);
+ DISALLOW_COPY_AND_ASSIGN(XmlActionExecutor);
};
-} // namespace xml
-} // namespace aapt
+} // namespace xml
+} // namespace aapt
#endif /* AAPT_XML_XMLPATTERN_H */
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index e4f41b0..932303e 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -38,18 +38,18 @@
* Base class for all XML nodes.
*/
class Node {
-public:
- Node* parent = nullptr;
- size_t lineNumber = 0;
- size_t columnNumber = 0;
- std::string comment;
- std::vector<std::unique_ptr<Node>> children;
+ public:
+ Node* parent = nullptr;
+ size_t lineNumber = 0;
+ size_t columnNumber = 0;
+ std::string comment;
+ std::vector<std::unique_ptr<Node>> children;
- virtual ~Node() = default;
+ virtual ~Node() = default;
- void addChild(std::unique_ptr<Node> child);
- virtual void accept(RawVisitor* visitor) = 0;
- virtual std::unique_ptr<Node> clone() = 0;
+ void addChild(std::unique_ptr<Node> child);
+ virtual void accept(RawVisitor* visitor) = 0;
+ virtual std::unique_ptr<Node> clone() = 0;
};
/**
@@ -58,178 +58,173 @@
*/
template <typename Derived>
class BaseNode : public Node {
-public:
- virtual void accept(RawVisitor* visitor) override;
+ public:
+ virtual void accept(RawVisitor* visitor) override;
};
/**
* A Namespace XML node. Can only have one child.
*/
class Namespace : public BaseNode<Namespace> {
-public:
- std::string namespacePrefix;
- std::string namespaceUri;
+ public:
+ std::string namespacePrefix;
+ std::string namespaceUri;
- std::unique_ptr<Node> clone() override;
+ std::unique_ptr<Node> clone() override;
};
struct AaptAttribute {
- Maybe<ResourceId> id;
- aapt::Attribute attribute;
+ Maybe<ResourceId> id;
+ aapt::Attribute attribute;
};
/**
* An XML attribute.
*/
struct Attribute {
- std::string namespaceUri;
- std::string name;
- std::string value;
+ std::string namespaceUri;
+ std::string name;
+ std::string value;
- Maybe<AaptAttribute> compiledAttribute;
- std::unique_ptr<Item> compiledValue;
+ Maybe<AaptAttribute> compiledAttribute;
+ std::unique_ptr<Item> compiledValue;
};
/**
* An Element XML node.
*/
class Element : public BaseNode<Element> {
-public:
- std::string namespaceUri;
- std::string name;
- std::vector<Attribute> attributes;
+ public:
+ std::string namespaceUri;
+ std::string name;
+ std::vector<Attribute> attributes;
- Attribute* findAttribute(const StringPiece& ns, const StringPiece& name);
- xml::Element* findChild(const StringPiece& ns, const StringPiece& name);
- xml::Element* findChildWithAttribute(const StringPiece& ns, const StringPiece& name,
- const StringPiece& attrNs,
- const StringPiece& attrName,
- const StringPiece& attrValue);
- std::vector<xml::Element*> getChildElements();
- std::unique_ptr<Node> clone() override;
+ Attribute* findAttribute(const StringPiece& ns, const StringPiece& name);
+ xml::Element* findChild(const StringPiece& ns, const StringPiece& name);
+ xml::Element* findChildWithAttribute(const StringPiece& ns,
+ const StringPiece& name,
+ const StringPiece& attrNs,
+ const StringPiece& attrName,
+ const StringPiece& attrValue);
+ std::vector<xml::Element*> getChildElements();
+ std::unique_ptr<Node> clone() override;
};
/**
* A Text (CDATA) XML node. Can not have any children.
*/
class Text : public BaseNode<Text> {
-public:
- std::string text;
+ public:
+ std::string text;
- std::unique_ptr<Node> clone() override;
+ std::unique_ptr<Node> clone() override;
};
/**
* An XML resource with a source, name, and XML tree.
*/
class XmlResource {
-public:
- ResourceFile file;
- std::unique_ptr<xml::Node> root;
+ public:
+ ResourceFile file;
+ std::unique_ptr<xml::Node> root;
};
/**
* Inflates an XML DOM from a text stream, logging errors to the logger.
* Returns the root node on success, or nullptr on failure.
*/
-std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source);
+std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag,
+ const Source& source);
/**
* Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger.
* Returns the root node on success, or nullptr on failure.
*/
-std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
- const Source& source);
+std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen,
+ IDiagnostics* diag, const Source& source);
Element* findRootElement(XmlResource* doc);
Element* findRootElement(Node* node);
/**
- * A visitor interface for the different XML Node subtypes. This will not traverse into
+ * A visitor interface for the different XML Node subtypes. This will not
+ * traverse into
* children. Use Visitor for that.
*/
class RawVisitor {
-public:
- virtual ~RawVisitor() = default;
+ public:
+ virtual ~RawVisitor() = default;
- virtual void visit(Namespace* node) {}
- virtual void visit(Element* node) {}
- virtual void visit(Text* text) {}
+ virtual void visit(Namespace* node) {}
+ virtual void visit(Element* node) {}
+ virtual void visit(Text* text) {}
};
/**
* Visitor whose default implementation visits the children nodes of any node.
*/
class Visitor : public RawVisitor {
-public:
- using RawVisitor::visit;
+ public:
+ using RawVisitor::visit;
- void visit(Namespace* node) override {
- visitChildren(node);
- }
+ void visit(Namespace* node) override { visitChildren(node); }
- void visit(Element* node) override {
- visitChildren(node);
- }
+ void visit(Element* node) override { visitChildren(node); }
- void visit(Text* text) override {
- visitChildren(text);
- }
+ void visit(Text* text) override { visitChildren(text); }
- void visitChildren(Node* node) {
- for (auto& child : node->children) {
- child->accept(this);
- }
+ void visitChildren(Node* node) {
+ for (auto& child : node->children) {
+ child->accept(this);
}
+ }
};
/**
* An XML DOM visitor that will record the package name for a namespace prefix.
*/
class PackageAwareVisitor : public Visitor, public IPackageDeclStack {
-public:
- using Visitor::visit;
+ public:
+ using Visitor::visit;
- void visit(Namespace* ns) override;
- Maybe<ExtractedPackage> transformPackageAlias(
- const StringPiece& alias, const StringPiece& localPackage) const override;
+ void visit(Namespace* ns) override;
+ Maybe<ExtractedPackage> transformPackageAlias(
+ const StringPiece& alias, const StringPiece& localPackage) const override;
-private:
- struct PackageDecl {
- std::string prefix;
- ExtractedPackage package;
- };
+ private:
+ struct PackageDecl {
+ std::string prefix;
+ ExtractedPackage package;
+ };
- std::vector<PackageDecl> mPackageDecls;
+ std::vector<PackageDecl> mPackageDecls;
};
// Implementations
template <typename Derived>
void BaseNode<Derived>::accept(RawVisitor* visitor) {
- visitor->visit(static_cast<Derived*>(this));
+ visitor->visit(static_cast<Derived*>(this));
}
template <typename T>
class NodeCastImpl : public RawVisitor {
-public:
- using RawVisitor::visit;
+ public:
+ using RawVisitor::visit;
- T* value = nullptr;
+ T* value = nullptr;
- void visit(T* v) override {
- value = v;
- }
+ void visit(T* v) override { value = v; }
};
template <typename T>
T* nodeCast(Node* node) {
- NodeCastImpl<T> visitor;
- node->accept(&visitor);
- return visitor.value;
+ NodeCastImpl<T> visitor;
+ node->accept(&visitor);
+ return visitor.value;
}
-} // namespace xml
-} // namespace aapt
+} // namespace xml
+} // namespace aapt
-#endif // AAPT_XML_DOM_H
+#endif // AAPT_XML_DOM_H
diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index a24d109..ce69df6 100644
--- a/tools/aapt2/xml/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -23,8 +23,8 @@
#include "util/StringPiece.h"
#include "xml/XmlUtil.h"
-#include <algorithm>
#include <expat.h>
+#include <algorithm>
#include <istream>
#include <ostream>
#include <queue>
@@ -36,263 +36,288 @@
namespace xml {
class XmlPullParser : public IPackageDeclStack {
-public:
- enum class Event {
- kBadDocument,
- kStartDocument,
- kEndDocument,
+ public:
+ enum class Event {
+ kBadDocument,
+ kStartDocument,
+ kEndDocument,
- kStartNamespace,
- kEndNamespace,
- kStartElement,
- kEndElement,
- kText,
- kComment,
- };
+ kStartNamespace,
+ kEndNamespace,
+ kStartElement,
+ kEndElement,
+ kText,
+ kComment,
+ };
- /**
- * Skips to the next direct descendant node of the given startDepth,
- * skipping namespace nodes.
- *
- * When nextChildNode returns true, you can expect Comments, Text, and StartElement events.
- */
- static bool nextChildNode(XmlPullParser* parser, size_t startDepth);
- static bool skipCurrentElement(XmlPullParser* parser);
- static bool isGoodEvent(Event event);
+ /**
+ * Skips to the next direct descendant node of the given startDepth,
+ * skipping namespace nodes.
+ *
+ * When nextChildNode returns true, you can expect Comments, Text, and
+ * StartElement events.
+ */
+ static bool nextChildNode(XmlPullParser* parser, size_t startDepth);
+ static bool skipCurrentElement(XmlPullParser* parser);
+ static bool isGoodEvent(Event event);
- explicit XmlPullParser(std::istream& in);
- ~XmlPullParser();
+ explicit XmlPullParser(std::istream& in);
+ ~XmlPullParser();
- /**
- * Returns the current event that is being processed.
- */
- Event getEvent() const;
+ /**
+ * Returns the current event that is being processed.
+ */
+ Event getEvent() const;
- const std::string& getLastError() const;
+ const std::string& getLastError() const;
- /**
- * Note, unlike XmlPullParser, the first call to next() will return
- * StartElement of the first element.
- */
- Event next();
+ /**
+ * Note, unlike XmlPullParser, the first call to next() will return
+ * StartElement of the first element.
+ */
+ Event next();
- //
- // These are available for all nodes.
- //
+ //
+ // These are available for all nodes.
+ //
- const std::string& getComment() const;
- size_t getLineNumber() const;
- size_t getDepth() const;
+ const std::string& getComment() const;
+ size_t getLineNumber() const;
+ size_t getDepth() const;
- /**
- * Returns the character data for a Text event.
- */
- const std::string& getText() const;
+ /**
+ * Returns the character data for a Text event.
+ */
+ const std::string& getText() const;
- //
- // Namespace prefix and URI are available for StartNamespace and EndNamespace.
- //
+ //
+ // Namespace prefix and URI are available for StartNamespace and EndNamespace.
+ //
- const std::string& getNamespacePrefix() const;
- const std::string& getNamespaceUri() const;
+ const std::string& getNamespacePrefix() const;
+ const std::string& getNamespaceUri() const;
- //
- // These are available for StartElement and EndElement.
- //
+ //
+ // These are available for StartElement and EndElement.
+ //
- const std::string& getElementNamespace() const;
- const std::string& getElementName() const;
+ const std::string& getElementNamespace() const;
+ const std::string& getElementName() const;
- /*
- * Uses the current stack of namespaces to resolve the package. Eg:
- * xmlns:app = "http://schemas.android.com/apk/res/com.android.app"
- * ...
- * android:text="@app:string/message"
- *
- * In this case, 'app' will be converted to 'com.android.app'.
- *
- * If xmlns:app="http://schemas.android.com/apk/res-auto", then
- * 'package' will be set to 'defaultPackage'.
- */
- Maybe<ExtractedPackage> transformPackageAlias(
- const StringPiece& alias, const StringPiece& localPackage) const override;
+ /*
+ * Uses the current stack of namespaces to resolve the package. Eg:
+ * xmlns:app = "http://schemas.android.com/apk/res/com.android.app"
+ * ...
+ * android:text="@app:string/message"
+ *
+ * In this case, 'app' will be converted to 'com.android.app'.
+ *
+ * If xmlns:app="http://schemas.android.com/apk/res-auto", then
+ * 'package' will be set to 'defaultPackage'.
+ */
+ Maybe<ExtractedPackage> transformPackageAlias(
+ const StringPiece& alias, const StringPiece& localPackage) const override;
- //
- // Remaining methods are for retrieving information about attributes
- // associated with a StartElement.
- //
- // Attributes must be in sorted order (according to the less than operator
- // of struct Attribute).
- //
+ //
+ // Remaining methods are for retrieving information about attributes
+ // associated with a StartElement.
+ //
+ // Attributes must be in sorted order (according to the less than operator
+ // of struct Attribute).
+ //
- struct Attribute {
- std::string namespaceUri;
- std::string name;
- std::string value;
+ struct Attribute {
+ std::string namespaceUri;
+ std::string name;
+ std::string value;
- int compare(const Attribute& rhs) const;
- bool operator<(const Attribute& rhs) const;
- bool operator==(const Attribute& rhs) const;
- bool operator!=(const Attribute& rhs) const;
- };
+ int compare(const Attribute& rhs) const;
+ bool operator<(const Attribute& rhs) const;
+ bool operator==(const Attribute& rhs) const;
+ bool operator!=(const Attribute& rhs) const;
+ };
- using const_iterator = std::vector<Attribute>::const_iterator;
+ using const_iterator = std::vector<Attribute>::const_iterator;
- const_iterator beginAttributes() const;
- const_iterator endAttributes() const;
- size_t getAttributeCount() const;
- const_iterator findAttribute(StringPiece namespaceUri, StringPiece name) const;
+ const_iterator beginAttributes() const;
+ const_iterator endAttributes() const;
+ size_t getAttributeCount() const;
+ const_iterator findAttribute(StringPiece namespaceUri,
+ StringPiece name) const;
-private:
- static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri);
- static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs);
- static void XMLCALL characterDataHandler(void* userData, const char* s, int len);
- static void XMLCALL endElementHandler(void* userData, const char* name);
- static void XMLCALL endNamespaceHandler(void* userData, const char* prefix);
- static void XMLCALL commentDataHandler(void* userData, const char* comment);
+ private:
+ static void XMLCALL startNamespaceHandler(void* userData, const char* prefix,
+ const char* uri);
+ static void XMLCALL startElementHandler(void* userData, const char* name,
+ const char** attrs);
+ static void XMLCALL characterDataHandler(void* userData, const char* s,
+ int len);
+ static void XMLCALL endElementHandler(void* userData, const char* name);
+ static void XMLCALL endNamespaceHandler(void* userData, const char* prefix);
+ static void XMLCALL commentDataHandler(void* userData, const char* comment);
- struct EventData {
- Event event;
- size_t lineNumber;
- size_t depth;
- std::string data1;
- std::string data2;
- std::vector<Attribute> attributes;
- };
+ struct EventData {
+ Event event;
+ size_t lineNumber;
+ size_t depth;
+ std::string data1;
+ std::string data2;
+ std::vector<Attribute> attributes;
+ };
- std::istream& mIn;
- XML_Parser mParser;
- char mBuffer[16384];
- std::queue<EventData> mEventQueue;
- std::string mLastError;
- const std::string mEmpty;
- size_t mDepth;
- std::stack<std::string> mNamespaceUris;
+ std::istream& mIn;
+ XML_Parser mParser;
+ char mBuffer[16384];
+ std::queue<EventData> mEventQueue;
+ std::string mLastError;
+ const std::string mEmpty;
+ size_t mDepth;
+ std::stack<std::string> mNamespaceUris;
- struct PackageDecl {
- std::string prefix;
- ExtractedPackage package;
- };
- std::vector<PackageDecl> mPackageAliases;
+ struct PackageDecl {
+ std::string prefix;
+ ExtractedPackage package;
+ };
+ std::vector<PackageDecl> mPackageAliases;
};
/**
* Finds the attribute in the current element within the global namespace.
*/
-Maybe<StringPiece> findAttribute(const XmlPullParser* parser, const StringPiece& name);
+Maybe<StringPiece> findAttribute(const XmlPullParser* parser,
+ const StringPiece& name);
/**
- * Finds the attribute in the current element within the global namespace. The attribute's value
+ * Finds the attribute in the current element within the global namespace. The
+ * attribute's value
* must not be the empty string.
*/
-Maybe<StringPiece> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece& name);
+Maybe<StringPiece> findNonEmptyAttribute(const XmlPullParser* parser,
+ const StringPiece& name);
//
// Implementation
//
-inline ::std::ostream& operator<<(::std::ostream& out, XmlPullParser::Event event) {
- switch (event) {
- case XmlPullParser::Event::kBadDocument: return out << "BadDocument";
- case XmlPullParser::Event::kStartDocument: return out << "StartDocument";
- case XmlPullParser::Event::kEndDocument: return out << "EndDocument";
- case XmlPullParser::Event::kStartNamespace: return out << "StartNamespace";
- case XmlPullParser::Event::kEndNamespace: return out << "EndNamespace";
- case XmlPullParser::Event::kStartElement: return out << "StartElement";
- case XmlPullParser::Event::kEndElement: return out << "EndElement";
- case XmlPullParser::Event::kText: return out << "Text";
- case XmlPullParser::Event::kComment: return out << "Comment";
- }
- return out;
+inline ::std::ostream& operator<<(::std::ostream& out,
+ XmlPullParser::Event event) {
+ switch (event) {
+ case XmlPullParser::Event::kBadDocument:
+ return out << "BadDocument";
+ case XmlPullParser::Event::kStartDocument:
+ return out << "StartDocument";
+ case XmlPullParser::Event::kEndDocument:
+ return out << "EndDocument";
+ case XmlPullParser::Event::kStartNamespace:
+ return out << "StartNamespace";
+ case XmlPullParser::Event::kEndNamespace:
+ return out << "EndNamespace";
+ case XmlPullParser::Event::kStartElement:
+ return out << "StartElement";
+ case XmlPullParser::Event::kEndElement:
+ return out << "EndElement";
+ case XmlPullParser::Event::kText:
+ return out << "Text";
+ case XmlPullParser::Event::kComment:
+ return out << "Comment";
+ }
+ return out;
}
-inline bool XmlPullParser::nextChildNode(XmlPullParser* parser, size_t startDepth) {
- Event event;
+inline bool XmlPullParser::nextChildNode(XmlPullParser* parser,
+ size_t startDepth) {
+ Event event;
- // First get back to the start depth.
- while (isGoodEvent(event = parser->next()) && parser->getDepth() > startDepth + 1) {}
+ // First get back to the start depth.
+ while (isGoodEvent(event = parser->next()) &&
+ parser->getDepth() > startDepth + 1) {
+ }
- // Now look for the first good node.
- while ((event != Event::kEndElement || parser->getDepth() > startDepth) && isGoodEvent(event)) {
- switch (event) {
- case Event::kText:
- case Event::kComment:
- case Event::kStartElement:
- return true;
- default:
- break;
- }
- event = parser->next();
+ // Now look for the first good node.
+ while ((event != Event::kEndElement || parser->getDepth() > startDepth) &&
+ isGoodEvent(event)) {
+ switch (event) {
+ case Event::kText:
+ case Event::kComment:
+ case Event::kStartElement:
+ return true;
+ default:
+ break;
}
- return false;
+ event = parser->next();
+ }
+ return false;
}
inline bool XmlPullParser::skipCurrentElement(XmlPullParser* parser) {
- int depth = 1;
- while (depth > 0) {
- switch (parser->next()) {
- case Event::kEndDocument:
- return true;
- case Event::kBadDocument:
- return false;
- case Event::kStartElement:
- depth++;
- break;
- case Event::kEndElement:
- depth--;
- break;
- default:
- break;
- }
+ int depth = 1;
+ while (depth > 0) {
+ switch (parser->next()) {
+ case Event::kEndDocument:
+ return true;
+ case Event::kBadDocument:
+ return false;
+ case Event::kStartElement:
+ depth++;
+ break;
+ case Event::kEndElement:
+ depth--;
+ break;
+ default:
+ break;
}
- return true;
+ }
+ return true;
}
inline bool XmlPullParser::isGoodEvent(XmlPullParser::Event event) {
- return event != Event::kBadDocument && event != Event::kEndDocument;
+ return event != Event::kBadDocument && event != Event::kEndDocument;
}
inline int XmlPullParser::Attribute::compare(const Attribute& rhs) const {
- int cmp = namespaceUri.compare(rhs.namespaceUri);
- if (cmp != 0) return cmp;
- return name.compare(rhs.name);
+ int cmp = namespaceUri.compare(rhs.namespaceUri);
+ if (cmp != 0) return cmp;
+ return name.compare(rhs.name);
}
inline bool XmlPullParser::Attribute::operator<(const Attribute& rhs) const {
- return compare(rhs) < 0;
+ return compare(rhs) < 0;
}
inline bool XmlPullParser::Attribute::operator==(const Attribute& rhs) const {
- return compare(rhs) == 0;
+ return compare(rhs) == 0;
}
inline bool XmlPullParser::Attribute::operator!=(const Attribute& rhs) const {
- return compare(rhs) != 0;
+ return compare(rhs) != 0;
}
-inline XmlPullParser::const_iterator XmlPullParser::findAttribute(StringPiece namespaceUri,
- StringPiece name) const {
- const auto endIter = endAttributes();
- const auto iter = std::lower_bound(beginAttributes(), endIter,
- std::pair<StringPiece, StringPiece>(namespaceUri, name),
- [](const Attribute& attr, const std::pair<StringPiece, StringPiece>& rhs) -> bool {
- int cmp = attr.namespaceUri.compare(0, attr.namespaceUri.size(),
- rhs.first.data(), rhs.first.size());
- if (cmp < 0) return true;
- if (cmp > 0) return false;
- cmp = attr.name.compare(0, attr.name.size(), rhs.second.data(), rhs.second.size());
- if (cmp < 0) return true;
- return false;
- }
- );
+inline XmlPullParser::const_iterator XmlPullParser::findAttribute(
+ StringPiece namespaceUri, StringPiece name) const {
+ const auto endIter = endAttributes();
+ const auto iter = std::lower_bound(
+ beginAttributes(), endIter,
+ std::pair<StringPiece, StringPiece>(namespaceUri, name),
+ [](const Attribute& attr,
+ const std::pair<StringPiece, StringPiece>& rhs) -> bool {
+ int cmp = attr.namespaceUri.compare(0, attr.namespaceUri.size(),
+ rhs.first.data(), rhs.first.size());
+ if (cmp < 0) return true;
+ if (cmp > 0) return false;
+ cmp = attr.name.compare(0, attr.name.size(), rhs.second.data(),
+ rhs.second.size());
+ if (cmp < 0) return true;
+ return false;
+ });
- if (iter != endIter && namespaceUri == iter->namespaceUri && name == iter->name) {
- return iter;
- }
- return endIter;
+ if (iter != endIter && namespaceUri == iter->namespaceUri &&
+ name == iter->name) {
+ return iter;
+ }
+ return endIter;
}
-} // namespace xml
-} // namespace aapt
+} // namespace xml
+} // namespace aapt
-#endif // AAPT_XML_PULL_PARSER_H
+#endif // AAPT_XML_PULL_PARSER_H
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
index a6ad79d..96de654 100644
--- a/tools/aapt2/xml/XmlUtil.h
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -26,9 +26,12 @@
namespace xml {
constexpr const char* kSchemaAuto = "http://schemas.android.com/apk/res-auto";
-constexpr const char* kSchemaPublicPrefix = "http://schemas.android.com/apk/res/";
-constexpr const char* kSchemaPrivatePrefix = "http://schemas.android.com/apk/prv/res/";
-constexpr const char* kSchemaAndroid = "http://schemas.android.com/apk/res/android";
+constexpr const char* kSchemaPublicPrefix =
+ "http://schemas.android.com/apk/res/";
+constexpr const char* kSchemaPrivatePrefix =
+ "http://schemas.android.com/apk/prv/res/";
+constexpr const char* kSchemaAndroid =
+ "http://schemas.android.com/apk/res/android";
constexpr const char* kSchemaTools = "http://schemas.android.com/tools";
constexpr const char* kSchemaAapt = "http://schemas.android.com/aapt";
@@ -36,17 +39,19 @@
* Result of extracting a package name from a namespace URI declaration.
*/
struct ExtractedPackage {
- /**
- * The name of the package. This can be the empty string, which means that the package
- * should be assumed to be the package being compiled.
- */
- std::string package;
+ /**
+ * The name of the package. This can be the empty string, which means that the
+ * package
+ * should be assumed to be the package being compiled.
+ */
+ std::string package;
- /**
- * True if the package's private namespace was declared. This means that private resources
- * are made visible.
- */
- bool privateNamespace;
+ /**
+ * True if the package's private namespace was declared. This means that
+ * private resources
+ * are made visible.
+ */
+ bool privateNamespace;
};
/**
@@ -57,7 +62,8 @@
* Special case: if namespaceUri is http://schemas.android.com/apk/res-auto,
* returns an empty package name.
*/
-Maybe<ExtractedPackage> extractPackageFromNamespace(const std::string& namespaceUri);
+Maybe<ExtractedPackage> extractPackageFromNamespace(
+ const std::string& namespaceUri);
/**
* Returns an XML Android namespace for the given package of the form:
@@ -68,31 +74,37 @@
*
* http://schemas.android.com/apk/prv/res/<package>
*/
-std::string buildPackageNamespace(const StringPiece& package, bool privateReference=false);
+std::string buildPackageNamespace(const StringPiece& package,
+ bool privateReference = false);
/**
- * Interface representing a stack of XML namespace declarations. When looking up the package
+ * Interface representing a stack of XML namespace declarations. When looking up
+ * the package
* for a namespace prefix, the stack is checked from top to bottom.
*/
struct IPackageDeclStack {
- virtual ~IPackageDeclStack() = default;
+ virtual ~IPackageDeclStack() = default;
- /**
- * Returns an ExtractedPackage struct if the alias given corresponds with a package declaration.
- */
- virtual Maybe<ExtractedPackage> transformPackageAlias(
- const StringPiece& alias, const StringPiece& localPackage) const = 0;
+ /**
+ * Returns an ExtractedPackage struct if the alias given corresponds with a
+ * package declaration.
+ */
+ virtual Maybe<ExtractedPackage> transformPackageAlias(
+ const StringPiece& alias, const StringPiece& localPackage) const = 0;
};
/**
- * Helper function for transforming the original Reference inRef to a fully qualified reference
- * via the IPackageDeclStack. This will also mark the Reference as private if the namespace of
+ * Helper function for transforming the original Reference inRef to a fully
+ * qualified reference
+ * via the IPackageDeclStack. This will also mark the Reference as private if
+ * the namespace of
* the package declaration was private.
*/
void transformReferenceFromNamespace(IPackageDeclStack* declStack,
- const StringPiece& localPackage, Reference* inRef);
+ const StringPiece& localPackage,
+ Reference* inRef);
-} // namespace xml
-} // namespace aapt
+} // namespace xml
+} // namespace aapt
#endif /* AAPT_XML_XMLUTIL_H */
diff --git a/tools/bit/Android.mk b/tools/bit/Android.mk
new file mode 100644
index 0000000..1c1291f
--- /dev/null
+++ b/tools/bit/Android.mk
@@ -0,0 +1,46 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+# ==========================================================
+# Build the host executable: protoc-gen-javastream
+# ==========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bit
+
+# This tool doesn't build on darwin.
+LOCAL_MODULE_HOST_OS := linux
+
+LOCAL_SRC_FILES := \
+ aapt.cpp \
+ adb.cpp \
+ command.cpp \
+ main.cpp \
+ make.cpp \
+ print.cpp \
+ util.cpp
+
+LOCAL_STATIC_LIBRARIES := \
+ libexpat \
+ libinstrumentation \
+ libjsoncpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libprotobuf-cpp-full
+
+include $(BUILD_HOST_EXECUTABLE)
+
diff --git a/tools/bit/aapt.cpp b/tools/bit/aapt.cpp
new file mode 100644
index 0000000..961b47c
--- /dev/null
+++ b/tools/bit/aapt.cpp
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "aapt.h"
+
+#include "command.h"
+#include "print.h"
+#include "util.h"
+
+#include <regex>
+
+const regex NS_REGEX("( *)N: ([^=]+)=(.*)");
+const regex ELEMENT_REGEX("( *)E: ([^ ]+) \\(line=(\\d+)\\)");
+const regex ATTR_REGEX("( *)A: ([^\\(=]+)[^=]*=\"([^\"]+)\".*");
+
+const string ANDROID_NS("http://schemas.android.com/apk/res/android");
+
+bool
+Apk::HasActivity(const string& className)
+{
+ string fullClassName = full_class_name(package, className);
+ const size_t N = activities.size();
+ for (size_t i=0; i<N; i++) {
+ if (activities[i] == fullClassName) {
+ return true;
+ }
+ }
+ return false;
+}
+
+struct Attribute {
+ string ns;
+ string name;
+ string value;
+};
+
+struct Element {
+ Element* parent;
+ string ns;
+ string name;
+ int lineno;
+ vector<Attribute> attributes;
+ vector<Element*> children;
+
+ /**
+ * Indentation in the xmltree dump. Might not be equal to the distance
+ * from the root because namespace rows (scopes) have their own indentation.
+ */
+ int depth;
+
+ Element();
+ ~Element();
+
+ string GetAttr(const string& ns, const string& name) const;
+ void FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse);
+
+};
+
+Element::Element()
+{
+}
+
+Element::~Element()
+{
+ const size_t N = children.size();
+ for (size_t i=0; i<N; i++) {
+ delete children[i];
+ }
+}
+
+string
+Element::GetAttr(const string& ns, const string& name) const
+{
+ const size_t N = attributes.size();
+ for (size_t i=0; i<N; i++) {
+ const Attribute& attr = attributes[i];
+ if (attr.ns == ns && attr.name == name) {
+ return attr.value;
+ }
+ }
+ return string();
+}
+
+void
+Element::FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse)
+{
+ const size_t N = children.size();
+ for (size_t i=0; i<N; i++) {
+ Element* child = children[i];
+ if (child->ns == ns && child->name == name) {
+ result->push_back(child);
+ }
+ if (recurse) {
+ child->FindElements(ns, name, result, recurse);
+ }
+ }
+}
+
+struct Scope {
+ Scope* parent;
+ int depth;
+ map<string,string> namespaces;
+
+ Scope(Scope* parent, int depth);
+};
+
+Scope::Scope(Scope* p, int d)
+ :parent(p),
+ depth(d)
+{
+ if (p != NULL) {
+ namespaces = p->namespaces;
+ }
+}
+
+
+string
+full_class_name(const string& packageName, const string& className)
+{
+ if (className.length() == 0) {
+ return "";
+ }
+ if (className[0] == '.') {
+ return packageName + className;
+ }
+ if (className.find('.') == string::npos) {
+ return packageName + "." + className;
+ }
+ return className;
+}
+
+string
+pretty_component_name(const string& packageName, const string& className)
+{
+ if (starts_with(packageName, className)) {
+ size_t pn = packageName.length();
+ size_t cn = className.length();
+ if (cn > pn && className[pn] == '.') {
+ return packageName + "/" + string(className, pn, string::npos);
+ }
+ }
+ return packageName + "/" + className;
+}
+
+int
+inspect_apk(Apk* apk, const string& filename)
+{
+ // Load the manifest xml
+ Command cmd("aapt");
+ cmd.AddArg("dump");
+ cmd.AddArg("xmltree");
+ cmd.AddArg(filename);
+ cmd.AddArg("AndroidManifest.xml");
+
+ int err;
+
+ string output = get_command_output(cmd, &err, false);
+ check_error(err);
+
+ // Parse the manifest xml
+ Scope* scope = new Scope(NULL, -1);
+ Element* root = NULL;
+ Element* current = NULL;
+ vector<string> lines;
+ split_lines(&lines, output);
+ for (size_t i=0; i<lines.size(); i++) {
+ const string& line = lines[i];
+ smatch match;
+ if (regex_match(line, match, NS_REGEX)) {
+ int depth = match[1].length() / 2;
+ while (depth < scope->depth) {
+ Scope* tmp = scope;
+ scope = scope->parent;
+ delete tmp;
+ }
+ scope = new Scope(scope, depth);
+ scope->namespaces[match[2]] = match[3];
+ } else if (regex_match(line, match, ELEMENT_REGEX)) {
+ Element* element = new Element();
+
+ string str = match[2];
+ size_t colon = str.find(':');
+ if (colon == string::npos) {
+ element->name = str;
+ } else {
+ element->ns = scope->namespaces[string(str, 0, colon)];
+ element->name.assign(str, colon+1, string::npos);
+ }
+ element->lineno = atoi(match[3].str().c_str());
+ element->depth = match[1].length() / 2;
+
+ if (root == NULL) {
+ current = element;
+ root = element;
+ } else {
+ while (element->depth <= current->depth && current->parent != NULL) {
+ current = current->parent;
+ }
+ element->parent = current;
+ current->children.push_back(element);
+ current = element;
+ }
+ } else if (regex_match(line, match, ATTR_REGEX)) {
+ if (current != NULL) {
+ Attribute attr;
+ string str = match[2];
+ size_t colon = str.find(':');
+ if (colon == string::npos) {
+ attr.name = str;
+ } else {
+ attr.ns = scope->namespaces[string(str, 0, colon)];
+ attr.name.assign(str, colon+1, string::npos);
+ }
+ attr.value = match[3];
+ current->attributes.push_back(attr);
+ }
+ }
+ }
+ while (scope != NULL) {
+ Scope* tmp = scope;
+ scope = scope->parent;
+ delete tmp;
+ }
+
+ // Package name
+ apk->package = root->GetAttr("", "package");
+ if (apk->package.size() == 0) {
+ print_error("%s:%d: Manifest root element doesn't contain a package attribute",
+ filename.c_str(), root->lineno);
+ delete root;
+ return 1;
+ }
+
+ // Instrumentation runner
+ vector<Element*> instrumentation;
+ root->FindElements("", "instrumentation", &instrumentation, true);
+ if (instrumentation.size() > 0) {
+ // TODO: How could we deal with multiple instrumentation tags?
+ // We'll just pick the first one.
+ apk->runner = instrumentation[0]->GetAttr(ANDROID_NS, "name");
+ }
+
+ // Activities
+ vector<Element*> activities;
+ root->FindElements("", "activity", &activities, true);
+ for (size_t i=0; i<activities.size(); i++) {
+ string name = activities[i]->GetAttr(ANDROID_NS, "name");
+ if (name.size() == 0) {
+ continue;
+ }
+ apk->activities.push_back(full_class_name(apk->package, name));
+ }
+
+ delete root;
+ return 0;
+}
+
diff --git a/tools/bit/aapt.h b/tools/bit/aapt.h
new file mode 100644
index 0000000..6aeb03f
--- /dev/null
+++ b/tools/bit/aapt.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef AAPT_H
+#define AAPT_H
+
+#include <string>
+#include <vector>
+
+using namespace std;
+
+struct Apk
+{
+ string package;
+ string runner;
+ vector<string> activities;
+
+ bool HasActivity(const string& className);
+};
+
+string full_class_name(const string& packageName, const string& className);
+string pretty_component_name(const string& packageName, const string& className);
+
+int inspect_apk(Apk* apk, const string& filename);
+
+#endif // AAPT_H
diff --git a/tools/bit/adb.cpp b/tools/bit/adb.cpp
new file mode 100644
index 0000000..0c8424d
--- /dev/null
+++ b/tools/bit/adb.cpp
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "adb.h"
+
+#include "command.h"
+#include "print.h"
+#include "util.h"
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <limits.h>
+
+#include <iostream>
+#include <istream>
+#include <streambuf>
+
+using namespace std;
+
+struct Buffer: public streambuf
+{
+ Buffer(char* begin, size_t size);
+};
+
+Buffer::Buffer(char* begin, size_t size)
+{
+ this->setg(begin, begin, begin + size);
+}
+
+int
+run_adb(const char* first, ...)
+{
+ Command cmd("adb");
+
+ if (first == NULL) {
+ return 0;
+ }
+
+ cmd.AddArg(first);
+
+ va_list args;
+ va_start(args, first);
+ while (true) {
+ const char* arg = va_arg(args, char*);
+ if (arg == NULL) {
+ break;
+ }
+ cmd.AddArg(arg);
+ }
+ va_end(args);
+
+ return run_command(cmd);
+}
+
+string
+get_system_property(const string& name, int* err)
+{
+ Command cmd("adb");
+ cmd.AddArg("shell");
+ cmd.AddArg("getprop");
+ cmd.AddArg(name);
+
+ return trim(get_command_output(cmd, err, false));
+}
+
+
+static uint64_t
+read_varint(int fd, int* err, bool* done)
+{
+ uint32_t bits = 0;
+ uint64_t result = 0;
+ while (true) {
+ uint8_t byte;
+ ssize_t amt = read(fd, &byte, 1);
+ if (amt == 0) {
+ *done = true;
+ return result;
+ } else if (amt < 0) {
+ return *err = errno;
+ }
+ result |= uint64_t(byte & 0x7F) << bits;
+ if ((byte & 0x80) == 0) {
+ return result;
+ }
+ bits += 7;
+ if (bits > 64) {
+ *err = -1;
+ return 0;
+ }
+ }
+}
+
+static char*
+read_sized_buffer(int fd, int* err, size_t* resultSize)
+{
+ bool done = false;
+ uint64_t size = read_varint(fd, err, &done);
+ if (*err != 0 || done) {
+ return NULL;
+ }
+ if (size == 0) {
+ *resultSize = 0;
+ return NULL;
+ }
+ // 10 MB seems like a reasonable limit.
+ if (size > 10*1024*1024) {
+ print_error("result buffer too large: %llu", size);
+ return NULL;
+ }
+ char* buf = (char*)malloc(size);
+ if (buf == NULL) {
+ print_error("Can't allocate a buffer of size for test results: %llu", size);
+ return NULL;
+ }
+ int pos = 0;
+ while (size - pos > 0) {
+ ssize_t amt = read(fd, buf+pos, size-pos);
+ if (amt == 0) {
+ // early end of pipe
+ print_error("Early end of pipe.");
+ *err = -1;
+ free(buf);
+ return NULL;
+ } else if (amt < 0) {
+ // error
+ *err = errno;
+ free(buf);
+ return NULL;
+ }
+ pos += amt;
+ }
+ *resultSize = (size_t)size;
+ return buf;
+}
+
+static int
+read_sized_proto(int fd, Message* message)
+{
+ int err = 0;
+ size_t size;
+ char* buf = read_sized_buffer(fd, &err, &size);
+ if (err != 0) {
+ if (buf != NULL) {
+ free(buf);
+ }
+ return err;
+ } else if (size == 0) {
+ if (buf != NULL) {
+ free(buf);
+ }
+ return 0;
+ } else if (buf == NULL) {
+ return -1;
+ }
+ Buffer buffer(buf, size);
+ istream in(&buffer);
+
+ err = message->ParseFromIstream(&in) ? 0 : -1;
+
+ free(buf);
+ return err;
+}
+
+static int
+skip_bytes(int fd, ssize_t size, char* scratch, int scratchSize)
+{
+ while (size > 0) {
+ ssize_t amt = size < scratchSize ? size : scratchSize;
+ fprintf(stderr, "skipping %lu/%ld bytes\n", size, amt);
+ amt = read(fd, scratch, amt);
+ if (amt == 0) {
+ // early end of pipe
+ print_error("Early end of pipe.");
+ return -1;
+ } else if (amt < 0) {
+ // error
+ return errno;
+ }
+ size -= amt;
+ }
+ return 0;
+}
+
+static int
+skip_unknown_field(int fd, uint64_t tag, char* scratch, int scratchSize) {
+ bool done;
+ int err;
+ uint64_t size;
+ switch (tag & 0x7) {
+ case 0: // varint
+ read_varint(fd, &err, &done);
+ if (err != 0) {
+ return err;
+ } else if (done) {
+ return -1;
+ } else {
+ return 0;
+ }
+ case 1:
+ return skip_bytes(fd, 8, scratch, scratchSize);
+ case 2:
+ size = read_varint(fd, &err, &done);
+ if (err != 0) {
+ return err;
+ } else if (done) {
+ return -1;
+ }
+ if (size > INT_MAX) {
+ // we'll be here a long time but this keeps it from overflowing
+ return -1;
+ }
+ return skip_bytes(fd, (ssize_t)size, scratch, scratchSize);
+ case 5:
+ return skip_bytes(fd, 4, scratch, scratchSize);
+ default:
+ print_error("bad wire type for tag 0x%lx\n", tag);
+ return -1;
+ }
+}
+
+static int
+read_instrumentation_results(int fd, char* scratch, int scratchSize,
+ InstrumentationCallbacks* callbacks)
+{
+ bool done = false;
+ int err = 0;
+ string result;
+ while (true) {
+ uint64_t tag = read_varint(fd, &err, &done);
+ if (done) {
+ // Done reading input (this is the only place that a stream end isn't an error).
+ return 0;
+ } else if (err != 0) {
+ return err;
+ } else if (tag == 0xa) { // test_status
+ TestStatus status;
+ err = read_sized_proto(fd, &status);
+ if (err != 0) {
+ return err;
+ }
+ callbacks->OnTestStatus(status);
+ } else if (tag == 0x12) { // session_status
+ SessionStatus status;
+ err = read_sized_proto(fd, &status);
+ if (err != 0) {
+ return err;
+ }
+ callbacks->OnSessionStatus(status);
+ } else {
+ err = skip_unknown_field(fd, tag, scratch, scratchSize);
+ if (err != 0) {
+ return err;
+ }
+ }
+ }
+ return 0;
+}
+
+int
+run_instrumentation_test(const string& packageName, const string& runner, const string& className,
+ InstrumentationCallbacks* callbacks)
+{
+ Command cmd("adb");
+ cmd.AddArg("shell");
+ cmd.AddArg("am");
+ cmd.AddArg("instrument");
+ cmd.AddArg("-w");
+ cmd.AddArg("-m");
+ if (className.length() > 0) {
+ cmd.AddArg("-e");
+ cmd.AddArg("class");
+ cmd.AddArg(className);
+ }
+ cmd.AddArg(packageName + "/" + runner);
+
+ print_command(cmd);
+
+ int fds[2];
+ pipe(fds);
+
+ pid_t pid = fork();
+
+ if (pid == -1) {
+ // fork error
+ return errno;
+ } else if (pid == 0) {
+ // child
+ while ((dup2(fds[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
+ close(fds[1]);
+ close(fds[0]);
+ const char* prog = cmd.GetProg();
+ char* const* argv = cmd.GetArgv();
+ char* const* env = cmd.GetEnv();
+ exec_with_path_search(prog, argv, env);
+ print_error("Unable to run command: %s", prog);
+ exit(1);
+ } else {
+ // parent
+ close(fds[1]);
+ string result;
+ const int size = 16*1024;
+ char* buf = (char*)malloc(size);
+ int err = read_instrumentation_results(fds[0], buf, size, callbacks);
+ free(buf);
+ int status;
+ waitpid(pid, &status, 0);
+ if (err != 0) {
+ return err;
+ }
+ if (WIFEXITED(status)) {
+ return WEXITSTATUS(status);
+ } else {
+ return -1;
+ }
+ }
+}
+
+/**
+ * Get the second to last bundle in the args list. Stores the last name found
+ * in last. If the path is not found or if the args list is empty, returns NULL.
+ */
+static const ResultsBundleEntry *
+find_penultimate_entry(const ResultsBundle& bundle, va_list args)
+{
+ const ResultsBundle* b = &bundle;
+ const char* arg = va_arg(args, char*);
+ while (arg) {
+ string last = arg;
+ arg = va_arg(args, char*);
+ bool found = false;
+ for (int i=0; i<b->entries_size(); i++) {
+ const ResultsBundleEntry& e = b->entries(i);
+ if (e.key() == last) {
+ if (arg == NULL) {
+ return &e;
+ } else if (e.has_value_bundle()) {
+ b = &e.value_bundle();
+ found = true;
+ }
+ }
+ }
+ if (!found) {
+ return NULL;
+ }
+ if (arg == NULL) {
+ return NULL;
+ }
+ }
+ return NULL;
+}
+
+string
+get_bundle_string(const ResultsBundle& bundle, bool* found, ...)
+{
+ va_list args;
+ va_start(args, found);
+ const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args);
+ va_end(args);
+ if (entry == NULL) {
+ *found = false;
+ return string();
+ }
+ if (entry->has_value_string()) {
+ *found = true;
+ return entry->value_string();
+ }
+ *found = false;
+ return string();
+}
+
+int32_t
+get_bundle_int(const ResultsBundle& bundle, bool* found, ...)
+{
+ va_list args;
+ va_start(args, found);
+ const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args);
+ va_end(args);
+ if (entry == NULL) {
+ *found = false;
+ return 0;
+ }
+ if (entry->has_value_int()) {
+ *found = true;
+ return entry->value_int();
+ }
+ *found = false;
+ return 0;
+}
+
+float
+get_bundle_float(const ResultsBundle& bundle, bool* found, ...)
+{
+ va_list args;
+ va_start(args, found);
+ const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args);
+ va_end(args);
+ if (entry == NULL) {
+ *found = false;
+ return 0;
+ }
+ if (entry->has_value_float()) {
+ *found = true;
+ return entry->value_float();
+ }
+ *found = false;
+ return 0;
+}
+
+double
+get_bundle_double(const ResultsBundle& bundle, bool* found, ...)
+{
+ va_list args;
+ va_start(args, found);
+ const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args);
+ va_end(args);
+ if (entry == NULL) {
+ *found = false;
+ return 0;
+ }
+ if (entry->has_value_double()) {
+ *found = true;
+ return entry->value_double();
+ }
+ *found = false;
+ return 0;
+}
+
+int64_t
+get_bundle_long(const ResultsBundle& bundle, bool* found, ...)
+{
+ va_list args;
+ va_start(args, found);
+ const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args);
+ va_end(args);
+ if (entry == NULL) {
+ *found = false;
+ return 0;
+ }
+ if (entry->has_value_long()) {
+ *found = true;
+ return entry->value_long();
+ }
+ *found = false;
+ return 0;
+}
+
diff --git a/tools/bit/adb.h b/tools/bit/adb.h
new file mode 100644
index 0000000..dca80c8
--- /dev/null
+++ b/tools/bit/adb.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef ADB_H
+#define ADB_H
+
+#include "instrumentation_data.pb.h"
+
+#include <string>
+
+using namespace android::am;
+using namespace google::protobuf;
+using namespace std;
+
+class InstrumentationCallbacks {
+public:
+ virtual void OnTestStatus(TestStatus& status) = 0;
+ virtual void OnSessionStatus(SessionStatus& status) = 0;
+};
+
+int run_adb(const char* first, ...);
+
+string get_system_property(const string& name, int* err);
+
+int run_instrumentation_test(const string& packageName, const string& runner,
+ const string& className, InstrumentationCallbacks* callbacks);
+
+string get_bundle_string(const ResultsBundle& bundle, bool* found, ...);
+int32_t get_bundle_int(const ResultsBundle& bundle, bool* found, ...);
+float get_bundle_float(const ResultsBundle& bundle, bool* found, ...);
+double get_bundle_double(const ResultsBundle& bundle, bool* found, ...);
+int64_t get_bundle_long(const ResultsBundle& bundle, bool* found, ...);
+
+#endif // ADB_H
diff --git a/tools/bit/command.cpp b/tools/bit/command.cpp
new file mode 100644
index 0000000..9a8449b
--- /dev/null
+++ b/tools/bit/command.cpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "command.h"
+
+#include "print.h"
+#include "util.h"
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+extern char **environ;
+
+Command::Command(const string& prog)
+ :prog(prog)
+{
+}
+
+Command::~Command()
+{
+}
+
+void
+Command::AddArg(const string& arg)
+{
+ args.push_back(arg);
+}
+
+void
+Command::AddEnv(const string& name, const string& value)
+{
+ env[name] = value;
+}
+
+const char*
+Command::GetProg() const
+{
+ return prog.c_str();
+}
+
+char *const *
+Command::GetArgv() const
+{
+ const int N = args.size();
+ char** result = (char**)malloc(sizeof(char*)*(N+2));
+ result[0] = strdup(prog.c_str());
+ for (int i=0; i<N; i++) {
+ result[i+1] = strdup(args[i].c_str());
+ }
+ result[N+1] = 0;
+ return result;
+}
+
+char *const *
+Command::GetEnv() const
+{
+ map<string,string> copy;
+ for (const char** p=(const char**)environ; *p != NULL; p++) {
+ char* name = strdup(*p);
+ char* value = strchr(name, '=');
+ *value = '\0';
+ value++;
+ copy[name] = value;
+ free(name);
+ }
+ for (map<string,string>::const_iterator it=env.begin(); it!=env.end(); it++) {
+ copy[it->first] = it->second;
+ }
+ char** result = (char**)malloc(sizeof(char*)*(copy.size()+1));
+ char** row = result;
+ for (map<string,string>::const_iterator it=copy.begin(); it!=copy.end(); it++) {
+ *row = (char*)malloc(it->first.size() + it->second.size() + 2);
+ strcpy(*row, it->first.c_str());
+ strcat(*row, "=");
+ strcat(*row, it->second.c_str());
+ row++;
+ }
+ *row = NULL;
+ return result;
+}
+
+string
+get_command_output(const Command& command, int* err, bool quiet)
+{
+ if (!quiet) {
+ print_command(command);
+ }
+
+ int fds[2];
+ pipe(fds);
+
+ pid_t pid = fork();
+
+ if (pid == -1) {
+ // fork error
+ *err = errno;
+ return string();
+ } else if (pid == 0) {
+ // child
+ while ((dup2(fds[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
+ close(fds[1]);
+ close(fds[0]);
+ const char* prog = command.GetProg();
+ char* const* argv = command.GetArgv();
+ char* const* env = command.GetEnv();
+ exec_with_path_search(prog, argv, env);
+ if (!quiet) {
+ print_error("Unable to run command: %s", prog);
+ }
+ exit(1);
+ } else {
+ // parent
+ close(fds[1]);
+ string result;
+ const int size = 16*1024;
+ char* buf = (char*)malloc(size);
+ while (true) {
+ ssize_t amt = read(fds[0], buf, size);
+ if (amt <= 0) {
+ break;
+ } else if (amt > 0) {
+ result.append(buf, amt);
+ }
+ }
+ free(buf);
+ int status;
+ waitpid(pid, &status, 0);
+ if (WIFEXITED(status)) {
+ *err = WEXITSTATUS(status);
+ return result;
+ } else {
+ *err = -1;
+ return string();
+ }
+ }
+}
+
+
+int
+run_command(const Command& command)
+{
+ print_command(command);
+
+ pid_t pid = fork();
+
+ if (pid == -1) {
+ // fork error
+ return errno;
+ } else if (pid == 0) {
+ // child
+ const char* prog = command.GetProg();
+ char* const* argv = command.GetArgv();
+ char* const* env = command.GetEnv();
+ exec_with_path_search(prog, argv, env);
+ print_error("Unable to run command: %s", prog);
+ exit(1);
+ } else {
+ // parent
+ int status;
+ waitpid(pid, &status, 0);
+ if (WIFEXITED(status)) {
+ return WEXITSTATUS(status);
+ } else {
+ return -1;
+ }
+ }
+}
+
+int
+exec_with_path_search(const char* prog, char const* const* argv, char const* const* envp)
+{
+ if (prog[0] == '/') {
+ return execve(prog, (char*const*)argv, (char*const*)envp);
+ } else {
+ char* pathEnv = strdup(getenv("PATH"));
+ if (pathEnv == NULL) {
+ return 1;
+ }
+ char* dir = pathEnv;
+ while (dir) {
+ char* next = strchr(dir, ':');
+ if (next != NULL) {
+ *next = '\0';
+ next++;
+ }
+ if (dir[0] == '/') {
+ struct stat st;
+ string executable = string(dir) + "/" + prog;
+ if (stat(executable.c_str(), &st) == 0) {
+ execve(executable.c_str(), (char*const*)argv, (char*const*)envp);
+ }
+ }
+ dir = next;
+ }
+ free(pathEnv);
+ return 1;
+ }
+}
+
diff --git a/tools/bit/command.h b/tools/bit/command.h
new file mode 100644
index 0000000..fb44900
--- /dev/null
+++ b/tools/bit/command.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef COMMAND_H
+#define COMMAND_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+using namespace std;
+
+struct Command
+{
+ Command(const string& prog);
+ ~Command();
+
+ void AddArg(const string& arg);
+ void AddEnv(const string& name, const string& value);
+
+ const char* GetProg() const;
+ char* const* GetArgv() const;
+ char* const* GetEnv() const;
+
+ string GetCommandline() const;
+
+ string prog;
+ vector<string> args;
+ map<string,string> env;
+};
+
+/**
+ * Run the command and collect stdout.
+ * Returns the exit code.
+ */
+string get_command_output(const Command& command, int* err, bool quiet=false);
+
+/**
+ * Run the command.
+ * Returns the exit code.
+ */
+int run_command(const Command& command);
+
+// Mac OS doesn't have execvpe. This is the same as execvpe.
+int exec_with_path_search(const char* prog, char const* const* argv, char const* const* envp);
+
+#endif // COMMAND_H
+
diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp
new file mode 100644
index 0000000..4974a44
--- /dev/null
+++ b/tools/bit/main.cpp
@@ -0,0 +1,984 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "aapt.h"
+#include "adb.h"
+#include "make.h"
+#include "print.h"
+#include "util.h"
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <google/protobuf/stubs/common.h>
+
+using namespace std;
+
+/**
+ * An entry from the command line for something that will be built, installed,
+ * and/or tested.
+ */
+struct Target {
+ bool build;
+ bool install;
+ bool test;
+ string pattern;
+ string name;
+ vector<string> actions;
+ Module module;
+
+ int testActionCount;
+
+ int testPassCount;
+ int testFailCount;
+ bool actionsWithNoTests;
+
+ Target(bool b, bool i, bool t, const string& p);
+};
+
+Target::Target(bool b, bool i, bool t, const string& p)
+ :build(b),
+ install(i),
+ test(t),
+ pattern(p),
+ testActionCount(0),
+ testPassCount(0),
+ testFailCount(0),
+ actionsWithNoTests(false)
+{
+}
+
+/**
+ * Command line options.
+ */
+struct Options {
+ // For help
+ bool runHelp;
+
+ // For tab completion
+ bool runTab;
+ string tabPattern;
+
+ // For build/install/test
+ bool reboot;
+ vector<Target*> targets;
+
+ Options();
+ ~Options();
+};
+
+Options::Options()
+ :runHelp(false),
+ runTab(false),
+ reboot(false),
+ targets()
+{
+}
+
+Options::~Options()
+{
+}
+
+struct InstallApk
+{
+ TrackedFile file;
+ bool alwaysInstall;
+ bool installed;
+
+ InstallApk();
+ InstallApk(const InstallApk& that);
+ InstallApk(const string& filename, bool always);
+ ~InstallApk() {};
+};
+
+InstallApk::InstallApk()
+{
+}
+
+InstallApk::InstallApk(const InstallApk& that)
+ :file(that.file),
+ alwaysInstall(that.alwaysInstall),
+ installed(that.installed)
+{
+}
+
+InstallApk::InstallApk(const string& filename, bool always)
+ :file(filename),
+ alwaysInstall(always),
+ installed(false)
+{
+}
+
+
+/**
+ * Record for an test that is going to be launched.
+ */
+struct TestAction {
+ TestAction();
+
+ // The package name from the apk
+ string packageName;
+
+ // The test runner class
+ string runner;
+
+ // The test class, or none if all tests should be run
+ string className;
+
+ // The original target that requested this action
+ Target* target;
+
+ // The number of tests that passed
+ int passCount;
+
+ // The number of tests that failed
+ int failCount;
+};
+
+TestAction::TestAction()
+ :passCount(0),
+ failCount(0)
+{
+}
+
+/**
+ * Record for an activity that is going to be launched.
+ */
+struct ActivityAction {
+ // The package name from the apk
+ string packageName;
+
+ // The test class, or none if all tests should be run
+ string className;
+};
+
+/**
+ * Callback class for the am instrument command.
+ */
+class TestResults: public InstrumentationCallbacks
+{
+public:
+ virtual void OnTestStatus(TestStatus& status);
+ virtual void OnSessionStatus(SessionStatus& status);
+
+ /**
+ * Set the TestAction that the tests are for.
+ * It will be updated with statistics as the tests run.
+ */
+ void SetCurrentAction(TestAction* action);
+
+private:
+ TestAction* m_currentAction;
+};
+
+void
+TestResults::OnTestStatus(TestStatus& status)
+{
+ bool found;
+// printf("OnTestStatus\n");
+// status.PrintDebugString();
+ int32_t resultCode = status.has_results() ? status.result_code() : 0;
+
+ if (!status.has_results()) {
+ return;
+ }
+ const ResultsBundle &results = status.results();
+
+ int32_t currentTestNum = get_bundle_int(results, &found, "current", NULL);
+ if (!found) {
+ currentTestNum = -1;
+ }
+
+ int32_t testCount = get_bundle_int(results, &found, "numtests", NULL);
+ if (!found) {
+ testCount = -1;
+ }
+
+ string className = get_bundle_string(results, &found, "class", NULL);
+ if (!found) {
+ return;
+ }
+
+ string testName = get_bundle_string(results, &found, "test", NULL);
+ if (!found) {
+ return;
+ }
+
+ if (resultCode == 0) {
+ // test passed
+ m_currentAction->passCount++;
+ m_currentAction->target->testPassCount++;
+ } else if (resultCode == 1) {
+ // test starting
+ ostringstream line;
+ line << "Running";
+ if (currentTestNum > 0) {
+ line << ": " << currentTestNum;
+ if (testCount > 0) {
+ line << " of " << testCount;
+ }
+ }
+ line << ": " << m_currentAction->target->name << ':' << className << "\\#" << testName;
+ print_one_line("%s", line.str().c_str());
+ } else if (resultCode == -2) {
+ // test failed
+ m_currentAction->failCount++;
+ m_currentAction->target->testFailCount++;
+ printf("%s\n%sFailed: %s:%s\\#%s%s\n", g_escapeClearLine, g_escapeRedBold,
+ m_currentAction->target->name.c_str(), className.c_str(),
+ testName.c_str(), g_escapeEndColor);
+
+ string stack = get_bundle_string(results, &found, "stack", NULL);
+ if (found) {
+ printf("%s\n", stack.c_str());
+ }
+ }
+}
+
+void
+TestResults::OnSessionStatus(SessionStatus& /*status*/)
+{
+ //status.PrintDebugString();
+}
+
+void
+TestResults::SetCurrentAction(TestAction* action)
+{
+ m_currentAction = action;
+}
+
+/**
+ * Prints the usage statement / help text.
+ */
+static void
+print_usage(FILE* out) {
+ fprintf(out, "usage: bit OPTIONS PATTERN\n");
+ fprintf(out, "\n");
+ fprintf(out, " Build, sync and test android code.\n");
+ fprintf(out, "\n");
+ fprintf(out, " The -b -i and -t options allow you to specify which phases\n");
+ fprintf(out, " you want to run. If none of those options are given, then\n");
+ fprintf(out, " all phases are run. If any of these options are provided\n");
+ fprintf(out, " then only the listed phases are run.\n");
+ fprintf(out, "\n");
+ fprintf(out, " OPTIONS\n");
+ fprintf(out, " -b Run a build\n");
+ fprintf(out, " -i Install the targets\n");
+ fprintf(out, " -t Run the tests\n");
+ fprintf(out, "\n");
+ fprintf(out, " -r If the runtime needs to be restarted, do a full reboot\n");
+ fprintf(out, " instead\n");
+ fprintf(out, "\n");
+ fprintf(out, " PATTERN\n");
+ fprintf(out, " One or more targets to build, install and test. The target\n");
+ fprintf(out, " names are the names that appear in the LOCAL_MODULE or\n");
+ fprintf(out, " LOCAL_PACKAGE_NAME variables in Android.mk or Android.bp files.\n");
+ fprintf(out, "\n");
+ fprintf(out, " Building and installing\n");
+ fprintf(out, " -----------------------\n");
+ fprintf(out, " The modules specified will be built and then installed. If the\n");
+ fprintf(out, " files are on the system partition, they will be synced and the\n");
+ fprintf(out, " attached device rebooted. If they are APKs that aren't on the\n");
+ fprintf(out, " system partition they are installed with adb install.\n");
+ fprintf(out, "\n");
+ fprintf(out, " For example:\n");
+ fprintf(out, " bit framework\n");
+ fprintf(out, " Builds framework.jar, syncs the system partition and reboots.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit SystemUI\n");
+ fprintf(out, " Builds SystemUI.apk, syncs the system partition and reboots.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit CtsProtoTestCases\n");
+ fprintf(out, " Builds this CTS apk, adb installs it, but does not run any\n");
+ fprintf(out, " tests.\n");
+ fprintf(out, "\n");
+ fprintf(out, " Running Unit Tests\n");
+ fprintf(out, " ------------------\n");
+ fprintf(out, " To run a unit test, list the test class names and optionally the\n");
+ fprintf(out, " test method after the module.\n");
+ fprintf(out, "\n");
+ fprintf(out, " For example:\n");
+ fprintf(out, " bit CtsProtoTestCases:*\n");
+ fprintf(out, " Builds this CTS apk, adb installs it, and runs all the tests\n");
+ fprintf(out, " contained in that apk.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit framework CtsProtoTestCases:*\n");
+ fprintf(out, " Builds the framework and the apk, syncs and reboots, then\n");
+ fprintf(out, " adb installs CtsProtoTestCases.apk, and runs all tests \n");
+ fprintf(out, " contained in that apk.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\n");
+ fprintf(out, " bit CtsProtoTestCases:android.util.proto.cts.ProtoOutputStreamBoolTest\n");
+ fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs all the\n");
+ fprintf(out, " tests in the ProtoOutputStreamBoolTest class.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\\#testWrite\n");
+ fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the testWrite\n");
+ fprintf(out, " test method on that class.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\\#testWrite,.ProtoOutputStreamBoolTest\\#testRepeated\n");
+ fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the testWrite\n");
+ fprintf(out, " and testRepeated test methods on that class.\n");
+ fprintf(out, "\n");
+ fprintf(out, " Launching an Activity\n");
+ fprintf(out, " ---------------------\n");
+ fprintf(out, " To launch an activity, specify the activity class name after\n");
+ fprintf(out, " the module name.\n");
+ fprintf(out, "\n");
+ fprintf(out, " For example:\n");
+ fprintf(out, " bit StatusBarTest:NotificationBuilderTest\n");
+ fprintf(out, " bit StatusBarTest:.NotificationBuilderTest\n");
+ fprintf(out, " bit StatusBarTest:com.android.statusbartest.NotificationBuilderTest\n");
+ fprintf(out, " Builds and installs StatusBarTest.apk, launches the\n");
+ fprintf(out, " com.android.statusbartest/.NotificationBuilderTest activity.\n");
+ fprintf(out, "\n");
+ fprintf(out, "\n");
+ fprintf(out, "usage: bit --tab ...\n");
+ fprintf(out, "\n");
+ fprintf(out, " Lists the targets in a format for tab completion. To get tab\n");
+ fprintf(out, " completion, add this to your bash environment:\n");
+ fprintf(out, "\n");
+ fprintf(out, " complete -C \"bit --tab\" bit\n");
+ fprintf(out, "\n");
+ fprintf(out, " Sourcing android's build/envsetup.sh will do this for you\n");
+ fprintf(out, " automatically.\n");
+ fprintf(out, "\n");
+ fprintf(out, "\n");
+ fprintf(out, "usage: bit --help\n");
+ fprintf(out, "usage: bit -h\n");
+ fprintf(out, "\n");
+ fprintf(out, " Print this help message\n");
+ fprintf(out, "\n");
+}
+
+
+/**
+ * Sets the appropriate flag* variables. If there is a problem with the
+ * commandline arguments, prints the help message and exits with an error.
+ */
+static void
+parse_args(Options* options, int argc, const char** argv)
+{
+ // Help
+ if (argc == 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) {
+ options->runHelp = true;
+ return;
+ }
+
+ // Tab
+ if (argc >= 4 && strcmp(argv[1], "--tab") == 0) {
+ options->runTab = true;
+ options->tabPattern = argv[3];
+ return;
+ }
+
+ // Normal usage
+ bool anyPhases = false;
+ bool gotPattern = false;
+ bool flagBuild = false;
+ bool flagInstall = false;
+ bool flagTest = false;
+ for (int i=1; i < argc; i++) {
+ string arg(argv[i]);
+ if (arg[0] == '-') {
+ for (size_t j=1; j<arg.size(); j++) {
+ switch (arg[j]) {
+ case '-':
+ break;
+ case 'b':
+ if (gotPattern) {
+ gotPattern = false;
+ flagInstall = false;
+ flagTest = false;
+ }
+ flagBuild = true;
+ anyPhases = true;
+ break;
+ case 'i':
+ if (gotPattern) {
+ gotPattern = false;
+ flagBuild = false;
+ flagTest = false;
+ }
+ flagInstall = true;
+ anyPhases = true;
+ break;
+ case 't':
+ if (gotPattern) {
+ gotPattern = false;
+ flagBuild = false;
+ flagInstall = false;
+ }
+ flagTest = true;
+ anyPhases = true;
+ break;
+ case 'r':
+ options->reboot = true;
+ break;
+ default:
+ fprintf(stderr, "Unrecognized option '%c'\n", arg[j]);
+ print_usage(stderr);
+ exit(1);
+ break;
+ }
+ }
+ } else {
+ Target* target = new Target(flagBuild || !anyPhases, flagInstall || !anyPhases,
+ flagTest || !anyPhases, arg);
+ size_t colonPos = arg.find(':');
+ if (colonPos == 0) {
+ fprintf(stderr, "Test / activity supplied without a module to build: %s\n",
+ arg.c_str());
+ print_usage(stderr);
+ exit(1);
+ } else if (colonPos == string::npos) {
+ target->name = arg;
+ } else {
+ target->name.assign(arg, 0, colonPos);
+ size_t beginPos = colonPos+1;
+ size_t commaPos;
+ while (true) {
+ commaPos = arg.find(',', beginPos);
+ if (commaPos == string::npos) {
+ if (beginPos != arg.size()) {
+ target->actions.push_back(string(arg, beginPos, commaPos));
+ }
+ break;
+ } else {
+ if (commaPos != beginPos) {
+ target->actions.push_back(string(arg, beginPos, commaPos-beginPos));
+ }
+ beginPos = commaPos+1;
+ }
+ }
+ }
+ options->targets.push_back(target);
+ gotPattern = true;
+ }
+ }
+ // If no pattern was supplied, give an error
+ if (options->targets.size() == 0) {
+ fprintf(stderr, "No PATTERN supplied.\n\n");
+ print_usage(stderr);
+ exit(1);
+ }
+}
+
+/**
+ * Get an environment variable.
+ * Exits with an error if it is unset or the empty string.
+ */
+static string
+get_required_env(const char* name, bool quiet)
+{
+ const char* value = getenv(name);
+ if (value == NULL || value[0] == '\0') {
+ if (!quiet) {
+ fprintf(stderr, "%s not set. Did you source build/envsetup.sh,"
+ " run lunch and do a build?\n", name);
+ }
+ exit(1);
+ }
+ return string(value);
+}
+
+/**
+ * Get the out directory.
+ *
+ * This duplicates the logic in build/make/core/envsetup.mk (which hasn't changed since 2011)
+ * so that we don't have to wait for get_build_var make invocation.
+ */
+string
+get_out_dir()
+{
+ const char* out_dir = getenv("OUT_DIR");
+ if (out_dir == NULL || out_dir[0] == '\0') {
+ const char* common_base = getenv("OUT_DIR_COMMON_BASE");
+ if (common_base == NULL || common_base[0] == '\0') {
+ // We don't prefix with buildTop because we cd there and it
+ // makes all the filenames long when being pretty printed.
+ return "out";
+ } else {
+ char pwd[PATH_MAX];
+ if (getcwd(pwd, PATH_MAX) == NULL) {
+ fprintf(stderr, "Your pwd is too long.\n");
+ exit(1);
+ }
+ const char* slash = strrchr(pwd, '/');
+ if (slash == NULL) {
+ slash = "";
+ }
+ string result(common_base);
+ result += slash;
+ return result;
+ }
+ }
+ return string(out_dir);
+}
+
+/**
+ * Check that a system property on the device matches the expected value.
+ * Exits with an error if they don't.
+ */
+static void
+check_device_property(const string& property, const string& expected)
+{
+ int err;
+ string deviceValue = get_system_property(property, &err);
+ check_error(err);
+ if (deviceValue != expected) {
+ print_error("There is a mismatch between the build you just did and the device you");
+ print_error("are trying to sync it to in the %s system property", property.c_str());
+ print_error(" build: %s", expected.c_str());
+ print_error(" device: %s", deviceValue.c_str());
+ exit(1);
+ }
+}
+
+/**
+ * Run the build, install, and test actions.
+ */
+void
+run_phases(vector<Target*> targets, bool reboot)
+{
+ int err = 0;
+
+ //
+ // Initialization
+ //
+
+ print_status("Initializing");
+
+ const string buildTop = get_required_env("ANDROID_BUILD_TOP", false);
+ const string buildProduct = get_required_env("TARGET_PRODUCT", false);
+ const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false);
+ const string buildType = get_required_env("TARGET_BUILD_TYPE", false);
+ const string buildDevice = get_build_var(buildTop, "TARGET_DEVICE", false);
+ const string buildId = get_build_var(buildTop, "BUILD_ID", false);
+ const string buildOut = get_out_dir();
+
+ // TODO: print_command("cd", buildTop.c_str());
+ chdir(buildTop.c_str());
+
+ // Get the modules for the targets
+ map<string,Module> modules;
+ read_modules(buildOut, buildDevice, &modules, false);
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ map<string,Module>::iterator mod = modules.find(target->name);
+ if (mod != modules.end()) {
+ target->module = mod->second;
+ } else {
+ print_error("Error: Could not find module: %s", target->name.c_str());
+ err = 1;
+ }
+ }
+ if (err != 0) {
+ exit(1);
+ }
+
+ // Choose the goals
+ vector<string> goals;
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ if (target->build) {
+ goals.push_back(target->name);
+ }
+ }
+
+ // Figure out whether we need to sync the system and which apks to install
+ string systemPath = buildOut + "/target/product/" + buildDevice + "/system/";
+ string dataPath = buildOut + "/target/product/" + buildDevice + "/data/";
+ bool syncSystem = false;
+ bool alwaysSyncSystem = false;
+ vector<InstallApk> installApks;
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ if (target->install) {
+ for (size_t j=0; j<target->module.installed.size(); j++) {
+ const string& file = target->module.installed[j];
+ // System partition
+ if (starts_with(file, systemPath)) {
+ syncSystem = true;
+ if (!target->build) {
+ // If a system partition target didn't get built then
+ // it won't change we will always need to do adb sync
+ alwaysSyncSystem = true;
+ }
+ continue;
+ }
+ // Apk in the data partition
+ if (starts_with(file, dataPath) && ends_with(file, ".apk")) {
+ // Always install it if we didn't build it because otherwise
+ // it will never have changed.
+ installApks.push_back(InstallApk(file, !target->build));
+ continue;
+ }
+ }
+ }
+ }
+ map<string,FileInfo> systemFilesBefore;
+ if (syncSystem && !alwaysSyncSystem) {
+ get_directory_contents(systemPath, &systemFilesBefore);
+ }
+
+ //
+ // Build
+ //
+
+ // Run the build
+ if (goals.size() > 0) {
+ print_status("Building");
+ err = build_goals(goals);
+ check_error(err);
+ }
+
+ //
+ // Install
+ //
+
+ // Sync the system partition and reboot
+ bool skipSync = false;
+ if (syncSystem) {
+ print_status("Syncing /system");
+
+ if (!alwaysSyncSystem) {
+ // If nothing changed and we weren't forced to sync, skip the reboot for speed.
+ map<string,FileInfo> systemFilesAfter;
+ get_directory_contents(systemPath, &systemFilesAfter);
+ skipSync = !directory_contents_differ(systemFilesBefore, systemFilesAfter);
+ }
+ if (skipSync) {
+ printf("Skipping sync because no files changed.\n");
+ } else {
+ // Do some sanity checks
+ check_device_property("ro.build.product", buildProduct);
+ check_device_property("ro.build.type", buildVariant);
+ check_device_property("ro.build.id", buildId);
+
+ // Stop & Sync
+ err = run_adb("shell", "stop", NULL);
+ check_error(err);
+ err = run_adb("remount", NULL);
+ check_error(err);
+ err = run_adb("sync", "system", NULL);
+ check_error(err);
+
+ if (reboot) {
+ print_status("Rebooting");
+
+ err = run_adb("reboot", NULL);
+ check_error(err);
+ err = run_adb("wait-for-device", NULL);
+ check_error(err);
+ } else {
+ print_status("Restarting the runtime");
+
+ err = run_adb("shell", "setprop", "sys.boot_completed", "0", NULL);
+ check_error(err);
+ err = run_adb("shell", "start", NULL);
+ check_error(err);
+ }
+
+ while (true) {
+ string completed = get_system_property("sys.boot_completed", &err);
+ check_error(err);
+ if (completed == "1") {
+ break;
+ }
+ sleep(2);
+ }
+ sleep(1);
+ err = run_adb("shell", "wm", "dismiss-keyguard", NULL);
+ check_error(err);
+ }
+ }
+
+ // Install APKs
+ if (installApks.size() > 0) {
+ print_status("Installing APKs");
+ for (size_t i=0; i<installApks.size(); i++) {
+ InstallApk& apk = installApks[i];
+ if (!apk.file.fileInfo.exists || apk.file.HasChanged()) {
+ // It didn't exist before or it changed, so int needs install
+ err = run_adb("install", "-r", apk.file.filename.c_str(), NULL);
+ check_error(err);
+ apk.installed = true;
+ } else {
+ printf("APK didn't change. Skipping install of %s\n", apk.file.filename.c_str());
+ }
+ }
+ }
+
+ //
+ // Actions
+ //
+
+ // Inspect the apks, and figure out what is an activity and what needs a test runner
+ bool printedInspecting = false;
+ vector<TestAction> testActions;
+ vector<ActivityAction> activityActions;
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ if (target->test) {
+ for (size_t j=0; j<target->module.installed.size(); j++) {
+ string filename = target->module.installed[j];
+
+ if (!ends_with(filename, ".apk")) {
+ continue;
+ }
+
+ if (!printedInspecting) {
+ printedInspecting = true;
+ print_status("Inspecting APKs");
+ }
+
+ Apk apk;
+ err = inspect_apk(&apk, filename);
+ check_error(err);
+
+ for (size_t k=0; k<target->actions.size(); k++) {
+ string actionString = target->actions[k];
+ if (actionString == "*") {
+ if (apk.runner.length() == 0) {
+ print_error("Error: Test requested for apk that doesn't"
+ " have an <instrumentation> tag: %s\n",
+ target->module.name.c_str());
+ exit(1);
+ }
+ TestAction action;
+ action.packageName = apk.package;
+ action.runner = apk.runner;
+ action.target = target;
+ testActions.push_back(action);
+ target->testActionCount++;
+ } else if (apk.HasActivity(actionString)) {
+ ActivityAction action;
+ action.packageName = apk.package;
+ action.className = full_class_name(apk.package, actionString);
+ activityActions.push_back(action);
+ } else {
+ if (apk.runner.length() == 0) {
+ print_error("Error: Test requested for apk that doesn't"
+ " have an <instrumentation> tag: %s\n",
+ target->module.name.c_str());
+ exit(1);
+ }
+ TestAction action;
+ action.packageName = apk.package;
+ action.runner = apk.runner;
+ action.className = full_class_name(apk.package, actionString);
+ action.target = target;
+ testActions.push_back(action);
+ target->testActionCount++;
+ }
+ }
+ }
+ }
+ }
+
+ // Run the instrumentation tests
+ TestResults testResults;
+ if (testActions.size() > 0) {
+ print_status("Running tests");
+ for (size_t i=0; i<testActions.size(); i++) {
+ TestAction& action = testActions[i];
+ testResults.SetCurrentAction(&action);
+ err = run_instrumentation_test(action.packageName, action.runner, action.className,
+ &testResults);
+ check_error(err);
+ if (action.passCount == 0 && action.failCount == 0) {
+ action.target->actionsWithNoTests = true;
+ }
+ int total = action.passCount + action.failCount;
+ printf("%sRan %d test%s for %s. ", g_escapeClearLine,
+ total, total > 1 ? "s" : "", action.target->name.c_str());
+ if (action.passCount == 0 && action.failCount == 0) {
+ printf("%s%d passed, %d failed%s\n", g_escapeYellowBold, action.passCount,
+ action.failCount, g_escapeEndColor);
+ } else if (action.failCount > 0) {
+ printf("%d passed, %s%d failed%s\n", action.passCount, g_escapeRedBold,
+ action.failCount, g_escapeEndColor);
+ } else {
+ printf("%s%d passed%s, %d failed\n", g_escapeGreenBold, action.passCount,
+ g_escapeEndColor, action.failCount);
+ }
+ }
+ }
+
+ // Launch the activity
+ if (activityActions.size() > 0) {
+ print_status("Starting activity");
+
+ if (activityActions.size() > 1) {
+ print_warning("Multiple activities specified. Will only start the first one:");
+ for (size_t i=0; i<activityActions.size(); i++) {
+ ActivityAction& action = activityActions[i];
+ print_warning(" %s",
+ pretty_component_name(action.packageName, action.className).c_str());
+ }
+ }
+
+ const ActivityAction& action = activityActions[0];
+ string componentName = action.packageName + "/" + action.className;
+ err = run_adb("shell", "am", "start", componentName.c_str(), NULL);
+ check_error(err);
+ }
+
+ //
+ // Print summary
+ //
+
+ printf("\n%s--------------------------------------------%s\n", g_escapeBold, g_escapeEndColor);
+
+ // Build
+ if (goals.size() > 0) {
+ printf("%sBuilt:%s\n", g_escapeBold, g_escapeEndColor);
+ for (size_t i=0; i<goals.size(); i++) {
+ printf(" %s\n", goals[i].c_str());
+ }
+ }
+
+ // Install
+ if (syncSystem) {
+ if (skipSync) {
+ printf("%sSkipped syncing /system partition%s\n", g_escapeBold, g_escapeEndColor);
+ } else {
+ printf("%sSynced /system partition%s\n", g_escapeBold, g_escapeEndColor);
+ }
+ }
+ if (installApks.size() > 0) {
+ bool printedTitle = false;
+ for (size_t i=0; i<installApks.size(); i++) {
+ const InstallApk& apk = installApks[i];
+ if (apk.installed) {
+ if (!printedTitle) {
+ printf("%sInstalled:%s\n", g_escapeBold, g_escapeEndColor);
+ printedTitle = true;
+ }
+ printf(" %s\n", apk.file.filename.c_str());
+ }
+ }
+ printedTitle = false;
+ for (size_t i=0; i<installApks.size(); i++) {
+ const InstallApk& apk = installApks[i];
+ if (!apk.installed) {
+ if (!printedTitle) {
+ printf("%sSkipped install:%s\n", g_escapeBold, g_escapeEndColor);
+ printedTitle = true;
+ }
+ printf(" %s\n", apk.file.filename.c_str());
+ }
+ }
+ }
+
+ // Tests
+ if (testActions.size() > 0) {
+ printf("%sRan tests:%s\n", g_escapeBold, g_escapeEndColor);
+ size_t maxNameLength = 0;
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ if (target->test) {
+ size_t len = target->name.length();
+ if (len > maxNameLength) {
+ maxNameLength = len;
+ }
+ }
+ }
+ string padding(maxNameLength, ' ');
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ if (target->testActionCount > 0) {
+ printf(" %s%s", target->name.c_str(), padding.c_str() + target->name.length());
+ if (target->actionsWithNoTests) {
+ printf(" %s%d passed, %d failed%s\n", g_escapeYellowBold,
+ target->testPassCount, target->testFailCount, g_escapeEndColor);
+ } else if (target->testFailCount > 0) {
+ printf(" %d passed, %s%d failed%s\n", target->testPassCount,
+ g_escapeRedBold, target->testFailCount, g_escapeEndColor);
+ } else {
+ printf(" %s%d passed%s, %d failed\n", g_escapeGreenBold,
+ target->testPassCount, g_escapeEndColor, target->testFailCount);
+ }
+ }
+ }
+ }
+ if (activityActions.size() > 1) {
+ printf("%sStarted Activity:%s\n", g_escapeBold, g_escapeEndColor);
+ const ActivityAction& action = activityActions[0];
+ printf(" %s\n", pretty_component_name(action.packageName, action.className).c_str());
+ }
+
+ printf("%s--------------------------------------------%s\n", g_escapeBold, g_escapeEndColor);
+}
+
+/**
+ * Implement tab completion of the target names from the all modules file.
+ */
+void
+run_tab_completion(const string& word)
+{
+ const string buildTop = get_required_env("ANDROID_BUILD_TOP", true);
+ const string buildProduct = get_required_env("TARGET_PRODUCT", false);
+ const string buildOut = get_out_dir();
+
+ chdir(buildTop.c_str());
+
+ string buildDevice = sniff_device_name(buildOut, buildProduct);
+
+ map<string,Module> modules;
+ read_modules(buildOut, buildDevice, &modules, true);
+
+ for (map<string,Module>::const_iterator it = modules.begin(); it != modules.end(); it++) {
+ if (starts_with(it->first, word)) {
+ printf("%s\n", it->first.c_str());
+ }
+ }
+}
+
+/**
+ * Main entry point.
+ */
+int
+main(int argc, const char** argv)
+{
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+ init_print();
+
+ Options options;
+ parse_args(&options, argc, argv);
+
+ if (options.runHelp) {
+ // Help
+ print_usage(stdout);
+ exit(0);
+ } else if (options.runTab) {
+ run_tab_completion(options.tabPattern);
+ exit(0);
+ } else {
+ // Normal run
+ run_phases(options.targets, options.reboot);
+ }
+
+ return 0;
+}
+
diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp
new file mode 100644
index 0000000..60b5687
--- /dev/null
+++ b/tools/bit/make.cpp
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "make.h"
+
+#include "command.h"
+#include "print.h"
+#include "util.h"
+
+#include <json/reader.h>
+#include <json/value.h>
+
+#include <fstream>
+#include <string>
+#include <map>
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+
+using namespace std;
+
+map<string,string> g_buildVars;
+
+string
+get_build_var(const string& buildTop, const string& name, bool quiet)
+{
+ int err;
+
+ map<string,string>::iterator it = g_buildVars.find(name);
+ if (it == g_buildVars.end()) {
+ Command cmd("make");
+ cmd.AddArg("--no-print-directory");
+ cmd.AddArg("-C");
+ cmd.AddArg(buildTop);
+ cmd.AddArg("-f");
+ cmd.AddArg("build/core/config.mk");
+ cmd.AddArg(string("dumpvar-") + name);
+ cmd.AddEnv("CALLED_FROM_SETUP", "true");
+ cmd.AddEnv("BUILD_SYSTEM", "build/core");
+
+ string output = trim(get_command_output(cmd, &err, quiet));
+ if (err == 0) {
+ g_buildVars[name] = output;
+ return output;
+ } else {
+ return string();
+ }
+ } else {
+ return it->second;
+ }
+}
+
+string
+sniff_device_name(const string& buildOut, const string& product)
+{
+ string match("ro.build.product=" + product);
+
+ string base(buildOut + "/target/product");
+ DIR* dir = opendir(base.c_str());
+ if (dir == NULL) {
+ return string();
+ }
+
+ dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+ if (entry->d_type == DT_DIR) {
+ string filename(base + "/" + entry->d_name + "/system/build.prop");
+ vector<string> lines;
+ split_lines(&lines, read_file(filename));
+ for (size_t i=0; i<lines.size(); i++) {
+ if (lines[i] == match) {
+ return entry->d_name;
+ }
+ }
+ }
+ }
+
+ closedir(dir);
+ return string();
+}
+
+void
+json_error(const string& filename, const char* error, bool quiet)
+{
+ if (!quiet) {
+ print_error("Unable to parse module info file (%s): %s", error, filename.c_str());
+ print_error("Have you done a full build?");
+ }
+ exit(1);
+}
+
+static void
+get_values(const Json::Value& json, const string& name, vector<string>* result)
+{
+ Json::Value nullValue;
+
+ const Json::Value& value = json.get(name, nullValue);
+ if (!value.isArray()) {
+ return;
+ }
+
+ const int N = value.size();
+ for (int i=0; i<N; i++) {
+ const Json::Value& child = value[i];
+ if (child.isString()) {
+ result->push_back(child.asString());
+ }
+ }
+}
+
+void
+read_modules(const string& buildOut, const string& device, map<string,Module>* result, bool quiet)
+{
+ string filename(string(buildOut + "/target/product/") + device + "/module-info.json");
+ std::ifstream stream(filename, std::ifstream::binary);
+
+ if (stream.fail()) {
+ if (!quiet) {
+ print_error("Unable to open module info file: %s", filename.c_str());
+ print_error("Have you done a full build?");
+ }
+ exit(1);
+ }
+
+ Json::Value json;
+ Json::Reader reader;
+ if (!reader.parse(stream, json)) {
+ json_error(filename, "can't parse json format", quiet);
+ return;
+ }
+
+ if (!json.isObject()) {
+ json_error(filename, "root element not an object", quiet);
+ return;
+ }
+
+ vector<string> names = json.getMemberNames();
+ const int N = names.size();
+ for (int i=0; i<N; i++) {
+ const string& name = names[i];
+
+ const Json::Value& value = json[name];
+ if (!value.isObject()) {
+ continue;
+ }
+
+ Module module;
+
+ module.name = name;
+ get_values(value, "class", &module.classes);
+ get_values(value, "path", &module.paths);
+ get_values(value, "installed", &module.installed);
+
+ // Only keep classes we can handle
+ for (ssize_t i = module.classes.size() - 1; i >= 0; i--) {
+ string cl = module.classes[i];
+ if (!(cl == "JAVA_LIBRARIES" || cl == "EXECUTABLES" || cl == "SHARED_LIBRARIES"
+ || cl == "APPS")) {
+ module.classes.erase(module.classes.begin() + i);
+ }
+ }
+ if (module.classes.size() == 0) {
+ continue;
+ }
+
+ // Only target modules (not host)
+ for (ssize_t i = module.installed.size() - 1; i >= 0; i--) {
+ string fn = module.installed[i];
+ if (!starts_with(fn, buildOut + "/target/")) {
+ module.installed.erase(module.installed.begin() + i);
+ }
+ }
+ if (module.installed.size() == 0) {
+ continue;
+ }
+
+ (*result)[name] = module;
+ }
+}
+
+int
+build_goals(const vector<string>& goals)
+{
+ Command cmd("make");
+ cmd.AddArg("-f");
+ cmd.AddArg("build/core/main.mk");
+ for (size_t i=0; i<goals.size(); i++) {
+ cmd.AddArg(goals[i]);
+ }
+
+ return run_command(cmd);
+}
+
diff --git a/tools/bit/make.h b/tools/bit/make.h
new file mode 100644
index 0000000..bb83c6e
--- /dev/null
+++ b/tools/bit/make.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef MAKE_H
+#define MAKE_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+using namespace std;
+
+struct Module
+{
+ string name;
+ vector<string> classes;
+ vector<string> paths;
+ vector<string> installed;
+};
+
+string get_build_var(const string& buildTop, const string& name, bool quiet);
+
+/**
+ * Poke around in the out directory and try to find a device name that matches
+ * our product. This is faster than running get_build_var and good enough for
+ * tab completion.
+ *
+ * Returns the empty string if we can't find one.
+ */
+string sniff_device_name(const string& buildOut, const string& product);
+
+void read_modules(const string& buildOut, const string& buildDevice,
+ map<string,Module>* modules, bool quiet);
+
+int build_goals(const vector<string>& goals);
+
+#endif // MAKE_H
diff --git a/tools/bit/print.cpp b/tools/bit/print.cpp
new file mode 100644
index 0000000..790e0b4
--- /dev/null
+++ b/tools/bit/print.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "print.h"
+
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "util.h"
+
+bool g_stdoutIsTty;
+char const* g_escapeBold;
+char const* g_escapeRedBold;
+char const* g_escapeGreenBold;
+char const* g_escapeYellowBold;
+char const* g_escapeUnderline;
+char const* g_escapeEndColor;
+char const* g_escapeClearLine;
+
+void
+init_print()
+{
+ if (isatty(fileno(stdout))) {
+ g_stdoutIsTty = true;
+ g_escapeBold = "\033[1m";
+ g_escapeRedBold = "\033[91m\033[1m";
+ g_escapeGreenBold = "\033[92m\033[1m";
+ g_escapeYellowBold = "\033[93m\033[1m";
+ g_escapeUnderline = "\033[4m";
+ g_escapeEndColor = "\033[0m";
+ g_escapeClearLine = "\033[K";
+ } else {
+ g_stdoutIsTty = false;
+ g_escapeBold = "";
+ g_escapeRedBold = "";
+ g_escapeGreenBold = "";
+ g_escapeYellowBold = "";
+ g_escapeUnderline = "";
+ g_escapeEndColor = "";
+ g_escapeClearLine = "";
+ }
+}
+
+void
+print_status(const char* format, ...)
+{
+ printf("\n%s%s", g_escapeBold, g_escapeUnderline);
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stdout, format, args);
+ va_end(args);
+
+ printf("%s\n", g_escapeEndColor);
+}
+
+void
+print_command(const Command& command)
+{
+ fputs(g_escapeBold, stdout);
+ for (map<string,string>::const_iterator it=command.env.begin(); it!=command.env.end(); it++) {
+ fputs(it->first.c_str(), stdout);
+ fputc('=', stdout);
+ fputs(escape_for_commandline(it->second.c_str()).c_str(), stdout);
+ putc(' ', stdout);
+ }
+ fputs(command.prog.c_str(), stdout);
+ for (vector<string>::const_iterator it=command.args.begin(); it!=command.args.end(); it++) {
+ putc(' ', stdout);
+ fputs(escape_for_commandline(it->c_str()).c_str(), stdout);
+ }
+ fputs(g_escapeEndColor, stdout);
+ fputc('\n', stdout);
+}
+
+void
+print_error(const char* format, ...)
+{
+ fputs(g_escapeRedBold, stderr);
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+
+ fputs(g_escapeEndColor, stderr);
+ fputc('\n', stderr);
+}
+
+void
+print_warning(const char* format, ...)
+{
+ fputs(g_escapeYellowBold, stderr);
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+
+ fputs(g_escapeEndColor, stderr);
+ fputc('\n', stderr);
+}
+
+void
+print_one_line(const char* format, ...)
+{
+ if (g_stdoutIsTty) {
+ struct winsize ws;
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
+ int size = ws.ws_col + 1;
+ char* buf = (char*)malloc(size);
+
+ va_list args;
+ va_start(args, format);
+ vsnprintf(buf, size, format, args);
+ va_end(args);
+
+ printf("%s%s\r", buf, g_escapeClearLine);
+ free(buf);
+
+ fflush(stdout);
+ } else {
+ va_list args;
+ va_start(args, format);
+ vfprintf(stdout, format, args);
+ va_end(args);
+ printf("\n");
+ }
+}
+
+void
+check_error(int err)
+{
+ if (err != 0) {
+ fputc('\n', stderr);
+ print_error("Stopping due to errors.");
+ exit(1);
+ }
+}
+
+
diff --git a/tools/bit/print.h b/tools/bit/print.h
new file mode 100644
index 0000000..b6c3e9a
--- /dev/null
+++ b/tools/bit/print.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef PRINT_H
+#define PRINT_H
+
+#include "command.h"
+
+extern bool g_stdoutIsTty;
+extern char const* g_escapeBold;
+extern char const* g_escapeRedBold;
+extern char const* g_escapeGreenBold;
+extern char const* g_escapeYellowBold;
+extern char const* g_escapeUnderline;
+extern char const* g_escapeEndColor;
+extern char const* g_escapeClearLine;
+
+void init_print();
+void print_status(const char* format, ...);
+void print_command(const Command& command);
+void print_error(const char* format, ...);
+void print_warning(const char* format, ...);
+void print_one_line(const char* format, ...);
+void check_error(int err);
+
+#endif // PRINT_H
diff --git a/tools/bit/util.cpp b/tools/bit/util.cpp
new file mode 100644
index 0000000..fc93bcb
--- /dev/null
+++ b/tools/bit/util.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "util.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <string.h>
+#include <unistd.h>
+
+
+FileInfo::FileInfo()
+{
+ memset(this, 0, sizeof(FileInfo));
+}
+
+FileInfo::FileInfo(const FileInfo& that)
+{
+ memcpy(this, &that, sizeof(FileInfo));
+}
+
+FileInfo::FileInfo(const string& filename)
+{
+ struct stat st;
+ int err = stat(filename.c_str(), &st);
+ if (err != 0) {
+ memset(this, 0, sizeof(FileInfo));
+ } else {
+ exists = true;
+ mtime = st.st_mtime;
+ ctime = st.st_ctime;
+ size = st.st_size;
+ }
+}
+
+bool
+FileInfo::operator==(const FileInfo& that) const
+{
+ return exists == that.exists
+ && mtime == that.mtime
+ && ctime == that.ctime
+ && size == that.size;
+}
+
+bool
+FileInfo::operator!=(const FileInfo& that) const
+{
+ return exists != that.exists
+ || mtime != that.mtime
+ || ctime != that.ctime
+ || size != that.size;
+}
+
+FileInfo::~FileInfo()
+{
+}
+
+TrackedFile::TrackedFile()
+ :filename(),
+ fileInfo()
+{
+}
+
+TrackedFile::TrackedFile(const TrackedFile& that)
+{
+ filename = that.filename;
+ fileInfo = that.fileInfo;
+}
+
+TrackedFile::TrackedFile(const string& file)
+ :filename(file),
+ fileInfo(file)
+{
+}
+
+TrackedFile::~TrackedFile()
+{
+}
+
+bool
+TrackedFile::HasChanged() const
+{
+ FileInfo updated(filename);
+ return !updated.exists || fileInfo != updated;
+}
+
+void
+get_directory_contents(const string& name, map<string,FileInfo>* results)
+{
+ int err;
+ DIR* dir = opendir(name.c_str());
+ if (dir == NULL) {
+ return;
+ }
+
+ dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
+ continue;
+ }
+ if (entry->d_type == DT_DIR) {
+ string subdir = name + "/" + entry->d_name;
+ get_directory_contents(subdir, results);
+ } else if (entry->d_type == DT_LNK || entry->d_type == DT_REG) {
+ string filename(name + "/" + entry->d_name);
+ (*results)[filename] = FileInfo(filename);
+ }
+ }
+
+ closedir(dir);
+}
+
+bool
+directory_contents_differ(const map<string,FileInfo>& before, const map<string,FileInfo>& after)
+{
+ if (before.size() != after.size()) {
+ return true;
+ }
+ map<string,FileInfo>::const_iterator b = before.begin();
+ map<string,FileInfo>::const_iterator a = after.begin();
+ while (b != before.end() && a != after.end()) {
+ if (b->first != a->first) {
+ return true;
+ }
+ if (a->second != b->second) {
+ return true;
+ }
+ a++;
+ b++;
+ }
+ return false;
+}
+
+string
+escape_quotes(const char* str)
+{
+ string result;
+ while (*str) {
+ if (*str == '"') {
+ result += '\\';
+ result += '"';
+ } else {
+ result += *str;
+ }
+ }
+ return result;
+}
+
+string
+escape_for_commandline(const char* str)
+{
+ if (strchr(str, '"') != NULL || strchr(str, ' ') != NULL
+ || strchr(str, '\t') != NULL) {
+ return escape_quotes(str);
+ } else {
+ return str;
+ }
+}
+
+static bool
+spacechr(char c)
+{
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+}
+
+string
+trim(const string& str)
+{
+ const ssize_t N = (ssize_t)str.size();
+ ssize_t begin = 0;
+ while (begin < N && spacechr(str[begin])) {
+ begin++;
+ }
+ ssize_t end = N - 1;
+ while (end >= begin && spacechr(str[end])) {
+ end--;
+ }
+ return string(str, begin, end-begin+1);
+}
+
+bool
+starts_with(const string& str, const string& prefix)
+{
+ return str.compare(0, prefix.length(), prefix) == 0;
+}
+
+bool
+ends_with(const string& str, const string& suffix)
+{
+ if (str.length() < suffix.length()) {
+ return false;
+ } else {
+ return str.compare(str.length()-suffix.length(), suffix.length(), suffix) == 0;
+ }
+}
+
+void
+split_lines(vector<string>* result, const string& str)
+{
+ const int N = str.length();
+ int begin = 0;
+ int end = 0;
+ for (; end < N; end++) {
+ const char c = str[end];
+ if (c == '\r' || c == '\n') {
+ if (begin != end) {
+ result->push_back(string(str, begin, end-begin));
+ }
+ begin = end+1;
+ }
+ }
+ if (begin != end) {
+ result->push_back(string(str, begin, end-begin));
+ }
+}
+
+string
+read_file(const string& filename)
+{
+ FILE* file = fopen(filename.c_str(), "r");
+ if (file == NULL) {
+ return string();
+ }
+
+ fseek(file, 0, SEEK_END);
+ int size = ftell(file);
+ fseek(file, 0, SEEK_SET);
+
+ char* buf = (char*)malloc(size);
+ fread(buf, 1, size, file);
+
+ string result(buf, size);
+
+ free(buf);
+ fclose(file);
+
+ return result;
+}
+
+
diff --git a/tools/bit/util.h b/tools/bit/util.h
new file mode 100644
index 0000000..718f147
--- /dev/null
+++ b/tools/bit/util.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+using namespace std;
+
+struct FileInfo
+{
+ bool exists;
+ time_t mtime;
+ time_t ctime;
+ off_t size;
+
+ FileInfo();
+ FileInfo(const FileInfo& that);
+ explicit FileInfo(const string& filename);
+ ~FileInfo();
+
+ bool operator==(const FileInfo& that) const;
+ bool operator!=(const FileInfo& that) const;
+};
+
+
+/**
+ * Record for a file that we are watching
+ */
+struct TrackedFile {
+ string filename;
+ FileInfo fileInfo;
+
+ TrackedFile();
+ TrackedFile(const TrackedFile& that);
+ explicit TrackedFile(const string& filename);
+ ~TrackedFile();
+
+ // Returns if the file has changed. If it doesn't currently exist, returns true.
+ bool HasChanged() const;
+};
+
+/**
+ * Get FileInfo structures recursively for all the files and symlinks in a directory.
+ * Does not traverse symlinks, but it does record them.
+ */
+void get_directory_contents(const string& dir, map<string,FileInfo>* results);
+
+bool directory_contents_differ(const map<string,FileInfo>& before,
+ const map<string,FileInfo>& after);
+
+string escape_quotes(const char* str);
+
+string escape_for_commandline(const char* str);
+
+string trim(const string& trim);
+
+bool starts_with(const string& str, const string& prefix);
+
+bool ends_with(const string& str, const string& suffix);
+
+void split_lines(vector<string>* result, const string& str);
+
+string read_file(const string& filename);
+
+#endif // UTIL_H
+
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 09ab657..6b4db42 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -413,7 +413,8 @@
}
@Override
- public int[] setNewConfiguration(Configuration arg0) throws RemoteException {
+ public int[] setNewDisplayOverrideConfiguration(Configuration arg0, int displayId)
+ throws RemoteException {
// TODO Auto-generated method stub
return null;
}
@@ -487,7 +488,7 @@
}
@Override
- public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1)
+ public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1, int arg2)
throws RemoteException {
// TODO Auto-generated method stub
return null;
diff --git a/tools/streaming_proto/main.cpp b/tools/streaming_proto/main.cpp
index d286213..5435728 100644
--- a/tools/streaming_proto/main.cpp
+++ b/tools/streaming_proto/main.cpp
@@ -191,38 +191,55 @@
switch (field.type()) {
case FieldDescriptorProto::TYPE_DOUBLE:
result |= FIELD_TYPE_DOUBLE;
+ break;
case FieldDescriptorProto::TYPE_FLOAT:
result |= FIELD_TYPE_FLOAT;
+ break;
case FieldDescriptorProto::TYPE_INT64:
result |= FIELD_TYPE_INT64;
+ break;
case FieldDescriptorProto::TYPE_UINT64:
result |= FIELD_TYPE_UINT64;
+ break;
case FieldDescriptorProto::TYPE_INT32:
result |= FIELD_TYPE_INT32;
+ break;
case FieldDescriptorProto::TYPE_FIXED64:
result |= FIELD_TYPE_FIXED64;
+ break;
case FieldDescriptorProto::TYPE_FIXED32:
result |= FIELD_TYPE_FIXED32;
+ break;
case FieldDescriptorProto::TYPE_BOOL:
result |= FIELD_TYPE_BOOL;
+ break;
case FieldDescriptorProto::TYPE_STRING:
result |= FIELD_TYPE_STRING;
+ break;
case FieldDescriptorProto::TYPE_MESSAGE:
result |= FIELD_TYPE_OBJECT;
+ break;
case FieldDescriptorProto::TYPE_BYTES:
result |= FIELD_TYPE_BYTES;
+ break;
case FieldDescriptorProto::TYPE_UINT32:
result |= FIELD_TYPE_UINT32;
+ break;
case FieldDescriptorProto::TYPE_ENUM:
result |= FIELD_TYPE_ENUM;
+ break;
case FieldDescriptorProto::TYPE_SFIXED32:
result |= FIELD_TYPE_SFIXED32;
+ break;
case FieldDescriptorProto::TYPE_SFIXED64:
result |= FIELD_TYPE_SFIXED64;
+ break;
case FieldDescriptorProto::TYPE_SINT32:
result |= FIELD_TYPE_SINT32;
+ break;
case FieldDescriptorProto::TYPE_SINT64:
result |= FIELD_TYPE_SINT64;
+ break;
default:
;
}